Criteria API query with non-direct relation - jpa

Good day!
There is SQL query that finely works:
select oi.nameshort from creditrequest c
join users u on u.id=c.user_id
join peoplemanagers pm on pm.people_id=u.people_id
-- join organiztion o on pm.organization_id=o.id
join organizationinfo oi on oi.organization_id=pm.organization_id
where oi.nameshort like 'Hydro%'
The problem arises when I try to translate SQL to JPA Criteria API. I need like restriction on table organizationinfo, but there is no direct reference from organization to it.
I try
pb.like(root.get(CreditRequestEntity_.userId)
.get(UsersEntity_.peopleId)
.get(PeopleEntity_.id)
.get(PeopleManagerEntity_.organizationId)
.get(OrganizationInfoEntity_.organizationId),
filter.getOrganization());
but it fails on .get(PeopleEntity_.id).
How to solve problem on Criteria API?
Classes:
#Entity
#Table(name = "creditrequest")
public class CreditRequestEntity {
...
#ManyToOne
#JoinColumn(name = "borrower_id")
private BorrowerEntity borrower;
...
}
#Entity
#Table(name = "borrower")
public class BorrowerEntity {
...
#ManyToOne
#JoinColumn(name = "organization_id")
private OrganizationEntity organizationId;
...
}
#Entity
#Table(name = "organization")
public class OrganizationEntity { ... }
#Entity
#Table(name = "organizationinfo")
public class OrganizationInfoEntity { ...
#Column(name = "nameshort")
private String nameShort;
#ManyToOne
#JoinColumn(name = "organization_id")
private OrganizationEntity organizationId;
...
}
Any class has field id as primary key.

if (filter.getOrganization() != null) {
List<Expression<String>> expressions = new ArrayList<>();
Subquery<OrganizationInfoEntity> psq = query.subquery(OrganizationInfoEntity.class);
Root<OrganizationInfoEntity> orgInfoRoot = psq.from(OrganizationInfoEntity.class);
psq.select(orgInfoRoot);
Join<OrganizationEntity, OrganizationInfoEntity> borrowerOrganizationInfoJoin = root.join(CreditRequestEntity_.borrower)
.join(BorrowerEntity_.organizationId).join(OrganizationEntity_.organizationInfo);
PredicateBuilder pipb = new PredicateBuilder(builder);
pb.add(builder.equal(borrowerOrganizationInfoJoin.get(OrganizationInfoEntity_.isActive), ActiveStatus.ACTIVE));
expressions.add(borrowerOrganizationInfoJoin.get(OrganizationInfoEntity_.nameShort));
pipb.like(filter.getOrganization(), expressions.toArray(new Expression[]{}));
psq.where(pipb.getWherePredicates());
pb.add(builder.exists(psq));
}

Related

Using the Criteria API and Metamodel API to Join two Middle table always cross join

This is a RBAC module,There is three basic table user,role and permission and middle mapping table user_role and role_permission.
#Entity
#Table(name = "USER")
public class User implements Serializable {
#Id
private String userId;
...
}
#Entity
#Table(name = "ROLE")
public class Role implements Serializable {
#Id
private String roleId;
...
}
#Entity
#Table(name = "PERMISSION")
public class Permission implements Serializable {
#Id
private String permissionId;
...
}
#Entity
#Table(name = "USER_ROLE")
public class UserRole implements Serializable {
#Id
#GenericGenerator(name = "uuidGenerator", strategy = "uuid")
#GeneratedValue(generator = "uuidGenerator")
#Column(name = "ID")
private String id;
#ManyToOne
#JoinColumn(name = "USERID")
private User user;
#ManyToOne
#JoinColumn(name = "ROLEID")
private Role role;
...
}
#Entity
#Table(name = "ROLE_PERMISSION")
public class RolePermission implements Serializable {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "PERMISSIONID")
private Permission permission;
#ManyToOne
#JoinColumn(name = "ROLEID")
private Role role;
...
}
and now i want to find all permission by user.id, SQL express like this:
select rp.* from Role_Permission rp,User_Role ur where ur.roleId = rp.roleId and ur.userId = :id
but by Criteria API:
public Predicate toPredicate(Root<RolePermission> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<Predicate>();
if (StringUtil.isNotEmpty(userId)) {
final Root<UserRole> userRoleRoot = criteriaQuery.from(UserRole.class);
Join<RolePermission,UserRole> join = root.join("role", JoinType.INNER);
Predicate predicate = criteriaBuilder.equal(join.get("roleId"), root.get("role").get("roleId"));
predicate = criteriaBuilder.and(predicate,criteriaBuilder.equal(userRoleRoot.<UserRole>get("user").get("id"), userId));
predicates.add(predicate);
}
return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
}
and query build result is:
select count(rolepermis0_.id) as col_0_0_ from role_permission rolepermis0_ inner join role role2_ on rolepermis0_.roleid=role2_.roleid cross join user_role userrole1_ where role2_.roleid=rolepermis0_.roleid and userrole1_.userid=?
but why here role_permission cross join user_role, how role_permission join user_role by criteria API ?
thanks a lot.
final Subquery<UserRole> userRoleSubquery = criteriaQuery.subquery(UserRole.class);
final Root<UserRole> userRole = userRoleSubquery.from(UserRole.class);
userRoleSubquery.select(userRole.<UserRole>get("id"));
userRoleSubquery.where(criteriaBuilder.equal(root.get("role").get("roleId"), userRole.get("role").get("roleId")), criteriaBuilder.equal(userRole.get("user").get("id"), userId));
Predicate predicate = criteriaBuilder.exists(userRoleSubquery);
predicates.add(predicate);

spring data jpa for multiple joined table

I have two tables: ProductUsage and Learner. ProductUsage have field Learner, Learner has fields id and guid. now I need to create a query to pull out all productUsage whose learner guid is in specified user ids:
SQL:
select * from product_usage
inner join learner
on product_usage.learner_id = learner.id
where
learner.guid in ("1234", "2345")
domain class:
#Data
#NoArgsConstructor
#Entity
#Table(name = "core_product_usage_increments")
public class ProductUsage {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
#JoinColumn(name = "learner_id", nullable = false)
private Learner learner;
#ManyToOne
#JoinColumn(name = "learning_language_id", nullable = false)
private Language language;
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "learners")
public class Learner {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#Column(name = "user_guid", nullable = false, unique = true)
private String guid;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
}
and repository class:
#Repository
public interface ProductUsageRepository extends CrudRepository<ProductUsage, Integer> {
#Query("SELECT p FROM ProductUsage p WHERE p.learnerGuid = :learnerGuid")
List<ProductUsage> findByLearnerGuid(String learnerGuid);
}
client class that call the repository
#Component
public class MyClient {
#Autowired
private ProductUsageRepository repository;
public MyClient(ProductUsageRepository repository) {
this.repository = repository;
}
public List<ProductUsage> getProductUageByLeanrerGuid(String learnerGuid) {
return repository.findByLearnerGuid(learnerGuid);
}
}
and my test:
#Test
public void testClient() throws Exception {
MyClient client = new MyClient(repository);
List<ProductUsage> results = client.getProductUageByLeanrerGuid("1234");
assertNotNull(result);
}
and it failed:
Caused by: java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: learnerGuid of: com.acme.domain.spectrum.ProductUsage [SELECT p FROM com.acme.domain.spectrum.ProductUsage p WHERE p.learnerGuid = :learnerGuid]
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1364)
at org.hibernate.ejb.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1300)
at org.hibernate.ejb.AbstractEntityManagerImpl.createQuery(AbstractEntityManagerImpl.java:294)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
it cannot recognize the 'learnerGuid' field in ProductUsage, but that's actually defined in Learner class. how can I perform the query that join multiple tables?
ProductUsage has no learnerGuid property, only learner. Try
#Query("SELECT p FROM ProductUsage p WHERE p.learner.guid = :learnerGuid")
If that doesn't work, I have another tip:
#Query("SELECT p FROM ProductUsage p join p.Learner l WHERE l.guid = :learnerGuid")
You do not have use a #query like you did
#Query("SELECT p FROM ProductUsage p WHERE p.learnerGuid = :learnerGuid")
List<ProductUsage> findByLearnerGuid(String learnerGuid);
Spring JPA framework can build the query by method name itself. Try this
List<ProductUsage> findByLearnerGuid(String learnerGuid);
or
List<ProductUsage> findByLearner_guid(String learnerGuid);
as you have a relation to Learner from ProductUsage the findBy method can traverse through the related tables and their fields. "_" gives the framework a clear indication that query by joining the Learner table where guid =?
Otherwise the framework tries below two combinations:
where learnerGuid=?
join learner where guid=?

one side set in many-to-many relation

I have three database tables: Customer, Product and PurchaseOrder (for mapping). I am using openjpa for peristence in java rest application.
To all of the tables I have corresponding entities:
Customer
#Entity
#Table(name = "customer")
#XmlRootElement
#NamedQueries({...})
public class Customer implements Serializable {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "customerId")
private Collection<PurchaseOrder> purchaseOrderCollection;
Product
#Entity
#Table(name = "product")
#XmlRootElement
#NamedQueries({...})
public class Product implements Serializable {
...
#OneToMany(cascade = CascadeType.ALL, mappedBy = "productId")
private Collection<PurchaseOrder> purchaseOrderCollection;
PurchaseOrder
#Entity
#Table(name = "purchase_order")
#XmlRootElement
#NamedQueries({..})
public class PurchaseOrder implements Serializable {
...
#Id
#Basic(optional = false)
#Column(name = "order_num")
private Integer orderNum;
#JoinColumn(name = "customer_id", referencedColumnName = "customer_id")
#ManyToOne(optional = false)
private Customer customer;
#JoinColumn(name = "product_id", referencedColumnName = "product_id")
#ManyToOne(optional = false)
private Product product;
What is the best way to get all the customers who ordered a product with specific id?
I could create namedQuery, I could build criteria with joins etc. But i think there could be a better way how to make use of the mapping entity (what would be point of this entity otherway?). Something like setting the productId to the purchaseOrder entity and then fetch all the customers via purchaseOrderCollection in customer entity? But i cannot figure it out. Is there other way than custom/named query or criteria building?
Thanks.
ok I figured it out, it can be this way
long productId = //get the id
Product product = entityManager.find(Product.class, productId);
Collection<PurchaseOrder> purchaseOrderCollection = product.getPurchaseOrderCollection();
if (purchaseOrderCollection != null) {
List<Integer> customers = new ArrayList<>(product.getPurchaseOrderCollection().size());
for (PurchaseOrder purchaseOrder : product.getPurchaseOrderCollection()) {
customers.add(purchaseOrder.getCustomerId());
}
return customers;
} else {
return Collections.EMPTY_LIST; // or null;
}
feel free to offer better sollution :)

JPA/Hibernate inheritance while loading: IllegalArgumentException, try to set wrong subclass

I'm using JBoss 6.1, I have the a JPA entagled situation, as a result I got the following error message:
IllegalArgumentException: Can not set EquipmentB field EquipmentCycleB.equipment to EquipmentA
suppose that each entity has an #Id annotated field:
A first hierarchy
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DIS", discriminatorType = DiscriminatorType.STRING, length = 1)
public abstract class Equipment { ... }
#Entity
#DiscriminatorValue("A")
public class EquipmentA extends Equipment { ... }
#Entity
#DiscriminatorValue("B")
public class EquipmentB extends Equipment { ... }
A second hierarchy
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DIS", discriminatorType = DiscriminatorType.STRING, length = 10)
public abstract class EquipmentCycle {
...
}
#Entity
#DiscriminatorValue("A")
public class EquipmentCycleA extends EquipmentCycle {
#JoinColumn(name = "EQUIPMENT_ID", referencedColumnName = "ID")
#ManyToOne
private EquipmentA equipment;
...
}
#Entity
#DiscriminatorValue("B")
public class EquipmentCycleB extends EquipmentCycle {
#JoinColumn(name = "EQUIPMENT_ID", referencedColumnName = "ID")
#ManyToOne
private EquipmentB equipment;
...
}
So far nothing strange, go on, the class say Status
#Entity
public class State {
#JoinColumn(name = "ENTITY_ID", referencedColumnName = "ID", nullable = false)
#ManyToOne
private EnityWithState enityWithState;
#JoinColumn(name = "EQUIPMENT_ID", referencedColumnName = "ID")
#ManyToOne
private Equipment equipment;
#JoinColumn(name = "EQUIPMENT_CYCLE_ID", referencedColumnName = "ID")
#ManyToOne
private EquipmentCycle equipmentCycle;
...
}
and the class which the state belong to
public class EnityWithState {
...
#OneToMany(mappedBy = "enityWithState", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
private List<State> stateHistory;
...
}
Don't ask me why, I have just found it.
After some time the DB store the following data
Table Equipment
ID DIS
--------------------------------
1 A ...
2 B ...
Table EquipmentCycle
ID DIS EQUIPMENT_ID
--------------------------------
1 A 1
2 B 2
Table State
ID ENTITY_ID EQUIPMENT_ID EQUIPMENT_CYCLE_ID
---------------------------------------------
1 1 1 1
2 1 2 2
And finally, when I try lo load the EnityWithState with id 1, I got the following error:
IllegalArgumentException: Can not set EquipmentB field EquipmentCycleB.equipment to EquipmentA
Does anyone have any idea? I googled but I found nothing.
Could it be an Hibernate/JPA bug?
Thanks in advance for the help

JPA query many to one association

I want to build the following pseudo query
Select a From APDU a where a.group.id= :id
group is a field in APDU class of the type APDUGroup.class.
I just want to get a list of APDUs based on APDUGroup's id.
How do i do that using a standard JPA query?
UPDATE
Yes, I have tried the above query and tried other variations for hours before posting in S/O. Here is the generated SQL for the query above:
SELECT t1.ID, t1.status, t1.type, t1.modified, t1.response, t1.expectedSize, t1.created, t1.description, t1.sequence, t1.name, t1.command, t1.recurring, t1.auth, t1.createdBy, t1.APDUGroup, t1.modifiedBy FROM APDUGroup t0, APDU t1 WHERE ((t0.ID = ?) AND (t0.ID = t1.APDUGroup))
The query looks okay but nothing get selected from my table.
There are at least 100 APDUs with APDUGroup = 1 in my test database.
I'm using eclipselink as the JPA provider.
Given the following Entities:
#Entity
public class APDU implements Serializable {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private APDUGroup group;
//...
}
#Entity
public class APDUGroup implements Serializable {
#Id
#GeneratedValue
private Long id;
//...
}
The following query will return a list of APDUs for a given APDUGroup id:
select a from APDU a where a.group.id = :id
Oh, wait, that's your query :)
Entity 1:
#Entity
#Getter
#Setter
#Table(name = "invoices")
public class Invoice implements Serializable {
#Id
#GeneratedValue
#Column(name = "invoice_id", updatable = false, nullable = false)
private Long invoiceId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "person_id", referencedColumnName = "person_id", insertable = false, updatable = false, nullable = false)
private Person person;
//...
}
Entity 2:
#Entity
#Getter
#Setter
#Table(name = "people")
public class Person implements Serializable {
#Id
#GeneratedValue
#Column(name = "person_id", updatable = false, nullable = false)
private Long personId;
//...
}
Finally, Your Data Access Object (JPA Repository)
#Repository
public interface InvoiceRepository extends JpaRepository<Invoice, Long> {
#Query(value="SELECT x FROM Invoice x WHERE x.person.personId = :myPersonId")
List<Invoice> findInvoiceByPersonId (long myPersonId);
}
I hope this example has been helpful :)