How to update records that are dependent on a different table using JPA Criteria API? - postgresql

I have the following entities with the one-to-one relationship:
#NoArgsConstructor
#Data
#DynamicUpdate
#Table(name = "product")
#Entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.UUID)
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#Column(name = "feed", length = 100, nullable = false)
private String feed;
// Omitted columns
#ToString.Exclude
#OneToOne(mappedBy = "product", cascade = CascadeType.ALL)
private PushPermission pushPermission;
}
#Data
#NoArgsConstructor
#Table(name = "push_permission")
#Entity
public class PushPermission implements Serializable {
#Id
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
// Omitted columns
#ToString.Exclude
#OneToOne
#JoinColumn(name = "id")
#MapsId
private Product product;
}
I would like to update all records in PushPermission where feed (column from Product) is not equal to PROMO using JPA Criteria API.
I have used the following CriteriaUpdate:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<PushPermission> criteriaUpdate = cb.createCriteriaUpdate(PushPermission.class);
Root<PushPermission> root = criteriaUpdate.from(PushPermission.class);
criteriaUpdate.set("exampleField", true);
Predicate selectedProductsPredicate = root.get("id").in(ids);
Predicate skipFeedPredicate = cb.notEqual(root.get("product").get("feed"), "PROMO");
criteriaUpdate.where(cb.and(selectedProductsPredicate, skipFeedPredicate));
Query query = entityManager.createQuery(criteriaUpdate);
query.executeUpdate();
but I got the following error message:
ERROR: missing FROM-clause entry for table "p2_0"
Generated update statement by Hibernate:
update
push_permission
set
exampleField=?,
where
id in(?,?)
and p2_0.feed!=?
Besides I tried to use joining:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaUpdate<PushPermission> criteriaUpdate = cb.createCriteriaUpdate(PushPermission.class);
Root<PushPermission> root = criteriaUpdate.from(PushPermission.class);
Join<PushPermission, Product> productJoin = root.join("product");
criteriaUpdate.set("exampleField", true);
Predicate selectedProductsPredicate = root.get("id").in(ids);
Predicate skipFeedPredicate = cb.notEqual(productJoin.get("feed"), "PROMO");
criteriaUpdate.where(cb.and(selectedProductsPredicate, skipFeedPredicate));
Query query = entityManager.createQuery(criteriaUpdate);
query.executeUpdate();
but I got the following message:
The root node [me.foo.app.PushPermission] does not allow join/fetch
Hibernate didn't generate any update statement.
I use Postgres SQL 14.5 and I know I can do the native query which works:
update push_permission set exampleField=true from product where push_permission.id=product.id and product.feed<>'PROMO';
but I wonder I can do it with the use of JPA Criteria API.
I use Spring Boot 3.0.2 that implies Hibernate 6.

That's not yet possible, but support for that is on the roadmap. For now, you'd have to use an exists subquery to model this i.e.
update PushPermission p
set p.exampleField=true
where exists (
select 1
from product pr
where p.id=pr.id
and pr.feed<>'PROMO';
)

Related

Fetching newest results using CriteriaBuilder gives error with grouping

I am trying to fetch from my PostgreSQL database using JPA and CriteriaBuilder data about my newest documents. I can have a lot of records with the same instanceNumber, but with different version property.
Here is my current code which I want to select documents, where version is the highest. I can identify and group documents by instanceNumber property.
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<DocumentSearchData> criteriaQuery = criteriaBuilder.createQuery(DocumentSearchData.class);
Root<DocumentSearchData> documentRoot = criteriaQuery.from(DocumentSearchData.class);
criteriaQuery.groupBy(documentRoot.get("instanceNumber"));
criteriaQuery.multiselect(
documentRoot,
criteriaBuilder.max(documentRoot.get("version"))
);
TypedQuery<DocumentSearchData> typedQuery = entityManager.createQuery(criteriaQuery);
List<DocumentSearchData> documentList = typedQuery.getResultList();
Hibernate translates my query to that SELECT:
select
documentse0_.id as col_0_0_,
max(documentse0_.version) as col_1_0_
from
document_search_data documentse0_
group by
documentse0_.instance_number
But unfortunately I am getting error: ERROR: column "documentse0_.id" must appear in the GROUP BY clause or be used in an aggregate function
I can not add id to my GROUP BY because it will give me incorrect results.
My class properties:
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "document_search_data_generator")
#SequenceGenerator(name = "document_search_data_generator", sequenceName = "document_search_data_seq", allocationSize = 50)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "external_document_id", nullable = false, unique = true)
private Integer documentId;
#Column(name = "instance_number", nullable = false)
private String instanceNumber;
#Column(name = "version", nullable = false)
private Integer version;
#Column(name = "search_data", columnDefinition="TEXT")
private String searchData;
#OneToOne(targetEntity = Document.class, fetch = FetchType.LAZY)
#JoinColumn(nullable = false, name = "document_id")
private Document document;
After that I will have another problem - how to use it with Pageable object?

JPA Annotations - How to retrieve all entities with a specific column value

Let say I have an entity object Customer with an "OneToMany" relation to Order. I want that when ever a "Customer" get loaded, only his orders with the Id = 1234, 5678 get loaded to.
Any ideas?
#Entity
#Table(name = "Customer")
public class Customer extends TraceableJPA {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "customer_id")
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "Customer", targetEntity = Order.class)
#Column(name = "order_id", value = {"1234","5678"} (?))
#OrderBy("isrtdate ASC")
#BatchSize(size = 20)
private List<Order> orders = new ArrayList<Order>();
Hibernate
If you use hibernate Session and its abilites , you can always use #FilterJoinTable mechanism.
Check THIS article for more information.
Yet it is not global, you have to predefine this filter and then explicitly configure Session object to use it.
JPA
JPA in its standard has NO SUCH FUNCTIONALITY, for global relations filtering.
You can always filter it in your queries : )

Many to Many relationship JPA, EJB and new table in db

i'm working on javaEE (7) Entreprise application with EJB, JPA and JSF2.2 (NetBeans 8.0.2 and GF 4.1)
this is my design in JavaDB : 2 tables with a Many to Many relationship so a new table "Avoir" is generated.
a "Document" has several "Critere" and "Critere" can belong to several "Document".
my problem is when i generate Entities Classes from Databases with Eclipselink 2.1, i have only a "Document" and "Critere" classes, but no "Avoir" class.
my question is, how can i add row in "Avoir" table ?
NB : this the code of my 2 classes
Critere code :
#Entity
#Table(name = "CRITERE")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Critere.findAll", query = "SELECT c FROM Critere c"),
#NamedQuery(name = "Critere.findByIdcritere", query = "SELECT c FROM Critere c WHERE c.idcritere = :idcritere"),
// Others Query …
public class Critere implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "IDCRITERE")
// Other columns …
#JoinTable(name = "AVOIR", joinColumns = {
#JoinColumn(name = "IDCRITERE", referencedColumnName = "IDCRITERE")}, inverseJoinColumns = {
#JoinColumn(name = "IDDOC", referencedColumnName = "IDDOC")})
#ManyToMany
private Collection<Document> documentCollection;
// Other mappings ...
and Document code :
#Entity
#Table(name = "DOCUMENT")
#XmlRootElement
#NamedQueries({
#NamedQuery(name = "Document.findAll", query = "SELECT d FROM Document d"),
#NamedQuery(name = "Document.findByIddoc", query = "SELECT d FROM Document d WHERE d.iddoc = :iddoc"),
// Other Query …
public class Document implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "IDDOC")
private Integer iddoc;
// Other columns …
#ManyToMany(mappedBy = "documentCollection")
private Collection<Critere> critereCollection;
// Other mappings …
thank you :)
You don't need to worry about that. #ManyToMany is implemented using a join table (AVOIR in your case), and the persistence provider takes care of wiring it all up in the database. Your responsibility is only to maintain both sides of the relationship, meaning if you add one Critere to a Document, be sure that Document also has that Critere in the list.
#JoinTable defines the table which is used for relationship, joinColumns attribute defines the column which is a foreign key to source table (CRITERE) and inverseJoinColumns attribute defines the column which is a foreign key to target table (DOCUMENT).
Document doc = em.find(Document.class, 1L);
Critere cit = em.find(Critere .class, 1L);
// you just need to maintain both sides of the relationship
doc.getCritereCollection().add(crit);
crit.getDocumentCollection().add(doc);
em.merge(doc); // not really needed because these are attached entities (if executed inside of transaction)
This will add a row in AVOIR table, with value 1 in both columns.

JPA Criteria Subquery on JoinTable

How do I create an efficient JPA Criteria query to select a list of entities only if they exist in a join table? For example take the following three tables:
create table user (user_id int, lastname varchar(64));
create table workgroup (workgroup_id int, name varchar(64));
create table user_workgroup (user_id int, workgroup_id int); -- Join Table
The query in question (what I want JPA to produce) is:
select * from user where user_id in (select user_id from user_workgroup where workgroup_id = ?);
The following Criteria query will produce a similar result, but with two joins:
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.select(root);
Subquery<Long> subquery = cq.subquery(Long.class);
Root<User> subroot = subquery.from(User.class);
subquery.select(subroot.<Long>get("userId"));
Join<User, Workgroup> workgroupList = subroot.join("workgroupList");
subquery.where(cb.equal(workgroupList.get("workgroupId"), ?));
cq.where(cb.in(root.get("userId")).value(subquery));
getEntityManager().createQuery(cq).getResultList();
The fundamental problem seems to be that I'm using the #JoinTable annotation for the USER_WORKGROUP join table instead of a separate #Entity for the join table so it doesn't seem I can use USER_WORKGROUP as a Root in a criteria query.
Here are the entity classes:
#Entity
public class User {
#Id
#Column(name = "USER_ID")
private Long userId;
#Column(name = "LASTNAME")
private String lastname;
#ManyToMany(mappedBy = "userList")
private List<Workgroup> workgroupList;
}
#Entity
public class Workgroup {
#Id
#Column(name = "WORKGROUP_ID")
private Long workgroupId;
#Column(name = "NAME")
private String name;
#JoinTable(name = "USER_WORKGROUP", joinColumns = {
#JoinColumn(name = "WORKGROUP_ID", referencedColumnName = "WORKGROUP_ID", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID", nullable = false)})
#ManyToMany
private List<User> userList;
}
As far as I know, JPA essentially ignores the join table. The JPQL that you do would be
select distinct u from user u join u.workgroupList wg where wg.name = :wgName
for the Criteria query, you should be able to do:
Criteria c = session.createCriteria(User.class, "u");
c.createAlias("u.workgroupList", "wg");
c.add(Restrictions.eq("wg.name", groupName));
c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
there's no need to worry about the middle join table.

query entity with condition on ManyToMany relation

I have two Entites
#Entity
public Report()
#Id
#Column(name = "REPORT_ID")
private long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name="reports_projects",
joinColumns={#JoinColumn(name="report_id", referencedColumnName="REPORT_ID")},
inverseJoinColumns={#JoinColumn(name="project", referencedColumnName="PROJECT_ID")})
private List<Project> projects;
second is:
#Entity(name = "projects")
public class Project
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PROJECT_ID")
// seems like spring's jpa has issue hanlde "_" between the words
private long id;
#Column(name = "CODE", nullable = false)
private String code;
#Column(name = "DESCRIPTION", nullable = false)
private String description;
#Column(name = "CREATION_DATE", nullable = false)
private Date creationDate;
i'm tring to query reports by projects.code
tried few stuff like
#Query("select reports from org.jpp.domain.quicksearch.ReportQS reports inner join reports.projects p where p.code in :code")
And
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<QuickSearchResult> query = cb.createQuery(QuickSearchResult.class);
Metamodel m = em.getMetamodel();
EntityType<ReportQS> ReportQSMetaModel = m.entity(ReportQS.class);
Root<ReportQS> reportsQS = query.from(ReportQS.class);
Root<Project> projects = query.from(Project.class);
Join<ReportQS, Project> joinReportsProjects = reportsQS.join("projects");
Predicate condition = cb.equal(projects.get("code"),"gnrl");
query.select(reportsQS).where(condition);
TypedQuery<QuickSearchResult> q = em.createQuery(query);
I get empty result for both of the queries
Any idea how to get this to work ?
Thanks in advance,
Oak
Try following code:
String query = "select r from ReportQS r join r.projects p where p.code = :code";
List<ReportQS> reports = em.createQuery(query,ReportQS.class).setParameter("code","grnl").getResultList();
Make sure that ReportQS is name of entity class (in your sample code you have different class name and different entity name used in query).