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

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.

Related

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

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

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 : )

JPA Specification: Select all entities which have at least one param with attribute from list

I have 2 entities with relationship ManyToMany
#Entity
#Table
public class TranslationUnit implements Serializable {
#Id
private Long id;
#ManyToMany(mappedBy = "translationUnit", fetch = FetchType.EAGER)
private Set<Category> categories = new HashSet<>();
}
#Entity
#Table
public class Category implements Serializable {
#ManyToMany
#JoinTable(name = "category_translation_unit",
joinColumns = #JoinColumn(name = "categories_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "translation_units_id", referencedColumnName = "id"))
private Set<TranslationUnit> translationUnits = new HashSet<>();
}
In Category I have 1 field, which should be used for filtering:
String name;
I need to be able to specify list of Category names (List), and select those TranslationUnits which have at least one Category with specified name.
I have several other filtering options, which should be used together, and I successfully built Specifications for them. But I've stuck with this one.
Please help.
P.S. One of my existing Specifications looks like this:
Specification idSpec = (Specification) (r, q, cb) -> {
List<Predicate> predicates = new ArrayList<>();
if (!filterRequest.getTranslationUnitIds().isEmpty())
predicates.add(r.get(TranslationUnit_.id).in(filterRequest.getTranslationUnitIds()));
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
};
Good day. You could use IN for filtering translation units by category names list. I believe, it will look like this using Criteria API:
Root<TranslationUnit> itemsRoot = ...;
Join join = itemsRoot.join("categories");
List<Predicate> predicates = new ArrayList<>();
predicates(join.get("name").in(categoryNamesList));

Hibernate Postgresql select for update with outer join issue

I have faced with issue trying to select for update row using Spring data with Hibernate as JPA implementation and Postgresql.
Suppose we have entities:A,B,C.
public class A{
#Id
private Long id;
#OneToMany(fetch = FetchType.EAGER)
private Set<B> bSet;
#OneToMany(fetch = FetchType.EAGER)
private Set<C> cSet;
}
Suppose we want to select A with all related B and C entities for update i.e. with locking row related to A table.
#Query(SELECT a FROM A a
LEFT JOIN FETCH a.bSet
LEFT JOIN FETCH a.cSet
WHERE a.id=?)
#Lock(LockModeType.PESSIMISTIC_WRITE)
public A selectAndLockA(Long Aid);
The query will look like
SELECT a.column1, ... from tableA a LEFT JOIN tableB b ... FOR UPDATE of a,c
FOR UPDATE of a,c
The query will try to lock two tables what leads to exception like :
org.postgresql.util.PSQLException: ERROR: FOR UPDATE cannot be applied to the nullable side of an outer join
What I try to archive is locking only first table "FOR UPDATE OF a"
Is it possible to configure somehow or tell Hibernate to lock only first table.
This is not supported by PostreSQL. If you do an outer SELECT nothing can prevent somebody from inserting a row into the LEFT JOINED table thereby modifiying the result set you are looking at (e.g. the columns would not be NULL anymore on a repeated read).
For a detailed explanantion see here
It's been a long time since question was created, but I have a similar problem and hope my answer will help somebody.
Suppose that we have this JPA entities:
#Entity
#Table(name = "card_transactions")
public class CardTransactionsEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "card_trans_seq")
#SequenceGenerator(name = "card_trans_seq", sequenceName = "card_trans_seq")
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "ofd_id", referencedColumnName = "ofd_id"),
#JoinColumn(name = "receipt_id", referencedColumnName = "receipt_id")})
private ReceiptsEntity receipt;
#Column
#Enumerated(EnumType.STRING)
private CardTransactionStatus requestStatus;
...
}
#Entity
#Table(name = "receipts")
public class ReceiptsEntity {
#EmbeddedId
private OfdReceiptId id;
...
}
#Embeddable
public class OfdReceiptId implements Serializable {
#Column(name = "ofd_id")
#Enumerated(EnumType.STRING)
private OfdId ofdId;
#Column(name = "receipt_id")
private String receiptId;
...
}
And we want select CardTransactionsEntity with fetched ReceiptsEntity for pessimistic update only CardTransactionsEntity. This can be done using Hibernate and Spring Data JPA repository as
public interface CardTransactionRepository extends JpaRepository<CardTransactionsEntity, Long> {
#Query("select ct from CardTransactionsEntity ct left join fetch ct.receipt r where ct.requestStatus = :requestStatus")
#Lock(value = LockModeType.PESSIMISTIC_WRITE)
#QueryHints(value = {
#QueryHint(name = "javax.persistence.lock.timeout", value = "-2"), // LockOptions.SKIP_LOCKED
#QueryHint(name = "org.hibernate.lockMode.r", value = "NONE") // "r" is alias for ct.receipt and will excluded from PESSIMISTIC_WRITE
})
List<CardTransactionsEntity> loadCardTransactions(#Param("requestStatus") CardTransactionStatus requestStatus, Pageable pageable);
}
This repository method will execute query like
SELECT ct.*, r.* from card_transactions ct LEFT OUTER JOIN receipts r ON ct.ofd_id = r.ofd_id and ct.receipt_id = r.receipt_id WHERE ct.request_status=? LIMIT ? FOR UPDATE OF ct SKIP LOCKED
You can bypass this error with joining the tables with FetchType.LAZY. This fetch type is the default one and it is not required to specify for #OneToMany joins.
public class A{
#Id
private Long id;
#OneToMany
private Set<B> bSet;
#OneToMany
private Set<C> cSet;
}

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).