JPA

Το Java Persistance API (JPA) είναι ένα Java specification, μέρος της Java EE, για την ανάκτηση, αποθήκευση και διαχείριση δεδομένων, μεταξύ αντικειμένων της Java και της σχεσιακής βάσης δεδομένων. Θεωρείται η πιο standard προσέγγιση για Object to Relational Mapping (ORM) στη βιομηχανία της Java.

To JPA είναι ένα σύνολο από διεπαφές, οι οποίες απαιτούν μια υλοποίηση. Δημοφιλείς υλοποιήσεις είναι το Hibernate, EclipseLink και Apache OpenJPA. Η υλοποίηση του JPA συνήθως λέγεται persistence provider.

Επίσης, επιτρέπει στους developers, να δουλεύουν απευθείας με objects (POJOs), αντί με εντολές SQL. Η αντιστοίχιση των κλάσεων της Java, με τους πίνακες της βάσης δεδομένων πραγματοποιείται με annotations ή XML. Παρακάτω, δίνεται ένα παράδειγμα αποθήκευσης ενός entity στη βάση δεδομένων:

Entities

Ένα entity είναι ένα ελαφρύ, persistance domain object. Αντιπροσωπεύει έναν πίνακα στην βάση δεδομένων και κάθε στιγμιότυπο του entity, αντιστοιχεί σε μια γραμμή εκείνου του πίνακα. Μια entity κλάση πρέπει, να ικανοποιεί τις ακόλουθες απαιτήσεις:

Όλα τα πεδία αποθηκεύονται στη βάση δεδομένων, εκτός αν κάποιο έχει το annotation @Transient. Παρακάτω δίνεται ένα παράδειγμα ενος Entity. Αντιστοιχίζεται η κλάση Category, με τον πίνακα categories της βάσης δεδομένων.

                
                        @Entity
                        @Table(name = "categories")
                        public class Category{

                           @Id
                           @GeneratedValue(strategy = GenerationType.IDENTITY)
                           @Column(name = "id")
                           private int id;

                           @Column(name = "name")
                           @NotNull(message = "Please provide category name")
                           @Size(min = 3, max = 200, message = "Name must be between 3 and 200 characters")
                           private String name;

                           @Transient
                           private String thisWillNotBeSavedInTheDatabaseTable;

                           public Category() {}

                           //Getters - Setters ...

                        } 
                
            
Bean validation

Το Java API για τα JavaBeans Validation (Bean Validation) παρέχει έναν μηχανισμό, για να κάνουμε validate τα δεδομένα της εφαρμογής.

Παρέχει περιορισμούς Bean Validation, τα οποία εφαρμοζόνται στα πεδία των persistent κλάσεων.

Στο προηγούμενο παράδειγμα, το annotation @NotNull του πεδίου name, δηλώνει ότι το πεδίο αυτό είναι υποχρεωτικό. Εάν δημιουργηθεί ένα στιγμιότυπο Category, στο οποίο δεν έχει αρχικοποιηθεί το πεδίο name, τότε το Bean Validation θα προκαλέσει ένα validation error. Αντιστοίχα, το annotation @Size δηλώνει ότι το πεδίο name, πρέπει να έχει μήκος 3 εώς 200 χαρακτήρων.

Πρωτεύοντα κλειδιά στα Entities

Κάθε entity έχει ένα μοναδικό αναγνωριστικό αντικειμένου, δηλαδή ένα πρωτεύον κλειδί. Αυτό μπορεί, να είναι απλό ή σύνθετο.

Για να δηλώσουμε ένα απλό πρωτεύον κλειδί, χρησιμοποιούμε το annotation @Id.

Σύνθετο πρωτεύον κλειδί χρησιμοποιούμε, όταν το κλειδί αποτελείται από περισσότερα από ενα πεδία. Τότε το κλειδί πρέπει, να οριστεί σε μια κλάση πρωτεύοντος κλειδιού. Για να δηλώσουμε, ένα σύνθετο πρωτεύον κλειδί, χρησιμοποιούμε το annotation @EmbeddedId.

Παράδειγμα απλού πρωτεύοντος κλειδιού.

                
                        @Entity
                        @Table(name = "categories")
                        public class Category{

                           @Id
                           @GeneratedValue(strategy = GenerationType.IDENTITY)
                           @Column(name = "id")
                           private int id;

                           @Column(name = "name")
                           @NotNull(message = "Please provide category name")
                           @Size(min = 3, max = 200, message = "Name must be between 3 and 200 characters")
                           private String name;

                           public Category() {}

                           //Getters - Setters ...
                        } 
                
            

Παράδειγμα σύνθετου πρωτεύοντος κλειδιού.

                
                        @Embeddable 
                        public class OrderProductPrimaryKey implements Serializable{

                           private int orderId;

                           private int productId;

                           public OrderProductPrimaryKey() {}

                           public OrderProductPrimaryKey(int orderId, int productId) {
                                this.orderId = orderId;
                                this.productId = productId;
                           }

                           @Override 
                           public boolean equals(Object o) {
                              if (this == o) return true ;
                              if (!(o instanceof OrderProductPrimaryKey)) return false ;
                              OrderProductPrimaryKey that = (OrderProductPrimaryKey) o;    
                              return  Objects.equals(getOrderId(), that.getOrderId()) && Objects.equals(getProductId(), that.getProductId());
                           }

                           @Override 
                           public int hashCode() {
                              return  Objects.hash(getOrderId(), getProductId());
                           }

                           //Getters - Setters ...

                        } 
                
            
                
                        @Entity 
                        @Table(name = "order_products")
                        public class OrderProduct  {

                           @EmbeddedId 
                           private OrderProductPrimaryKey id;

                           @OneToOne (fetch = FetchType.LAZY)
                           @MapsId("orderId")
                           private Order order;

                           @OneToOne (fetch = FetchType.LAZY)
                           @MapsId("productId")
                           private Product product;

                           @NotNull (message = "Please provide quantity"))
                           @Column(name = "quantity")
                           private int quantity;

                           public OrderProduct() {}

                           //Getters - Setters ...

                        } 
                
            
Πολλαπλότητα στις σχέσεις των entities
Πολλαπλότητα Περιγραφή
1-1 Κάθε στιγμιότυπο του entity, συσχετίζεται με ένα μόνο στιγμιότυπο ενός άλλου entity. Χρησιμοποιούμε το annotation @OneToOne στο αντίστιοιχο πεδίο.
1-Ν Κάθε στιγμιότυπο του entity, συσχετίζεται με πολλά στιγμιότυπα ενός άλλου entity. Χρησιμοποιούμε το annotation @OneToMany στο αντίστιοιχο πεδίο.
Ν-1 Πολλά στιγμιότυπα του entity, συσχετίζονται με ένα μόνο στιγμιότυπο ενός άλλου entity. Χρησιμοποιούμε το annotation @ManyToOne στο αντίστιοιχο πεδίο.
Ν-Ν Πολλά στιγμιότυπα του entity, συσχετίζονται με πολλά στιγμιότυπα ενός άλλου entity. Χρησιμοποιούμε το annotation @ManyToMany στο αντίστιοιχο πεδίο.

Παρακάτω, δίνεται ένα παράδειγμα Ν-1. Στην βάση δεδομένων υπάρχουν δύο πίνακες products και categories, οι οποίοι αντιστιχίζονται με τα entities Product και Category. Ο πίνακας products έχει ένα ξένο κλειδί category_id, το οποίο αναφέρεται στο πρωτεύον κλειδί του πίνακα categories. Το entity Product έχει ένα πεδίο Category, το οποίο έχει annotation @ManyToOne. Αυτό το annotation σημαίνει ότι πολλά προιόντα μπορούν, να ανήκουν σε μία μόνο κατηγορία.

                
                        @Entity 
                        @Table(name = "products")
                        public class Product  {

                           @Id
                           @GeneratedValue(strategy = GenerationType.IDENTITY)
                           @Column(name = "id")
                           private int id;

                           @ManyToOne (optional = false)
                           @JoinColumn(name = "category_id")
                           @NotNull 
                           private Category category;

                           //Rest of the fields ...

                           public Product() {}

                           //Getters - Setters ...

                        } 
                
            
Entity Manager

Τα entities διαχειρίζονται από τον entity manager, ο οποίος αντιπροσωπεύεται από την διεπαφή javax.persistence.EntityManager. Κάθε στιγμιότυπο EntityManager είναι συσχετισμένο με ένα persistence context. Persistence context είναιένα σύνολο από διαχειριζόμενα στιγμιότυπα από entities , τα οποία υπάρχουν σε μια συγκεκριμένη αποθήκη δεδομένων. Ορίζει την εμβέλεια, μέσα στην οποία δημιουργούνται, ανακτούνται και διαγράφονται τα στιγμιότυπα των entities. Η διεπαφή EntityManager παρέχει μεθόδους για την αλληλεπίδραση με το Persistence Context.

Container-Managed Entity Managers

Στην περίπτωση του Container-Managed Entity Manager, ο EntityManager δημιουργείται και διαχειρίζεται από το container. Για να πάρουμε ένα στιγμιότυπο του EntityManager, τον κάνουμε inject.

                
                        @PersistenceContext 
                        EntityManager em;
                
            

Application-Managed Entity Managers

Στην περίπτωση του Application-Managed Entity Manager, πρέπει εμείς, να δημιουργήσουμε και να διαχειρίστουμε τον EntityManager. Για να πάρουμε ένα στιγμιότυπο του EntityManager, πρέπει πρώτα να κάνουμε inject ένα EntityManagerFactory.

                
                        @PersistenceUnit 
                        EntityManagerFactory emf;
                        EntityManager em = emf.createEntityManager();
                
            
JPQL

Η Java Persistence query language ορίζεται στο JPA specification και μας επιτρέπει να ορίσουμε ερωτήματα πάνω σε entities, τα οποία είναι αποθηκευμένα στη βάση δεδομένων.

Σύνταξη

Η σύνταξη της γλώσσας είναι παρόμοια με αυτή της SQL. Η SQL δουλεύει άμεσα με τη βάση δεδομένων και τις εγγραφές, ενώ η JPQL δουλεύει με τις κλάσεις Entities και τα στιγμιότυπα τους. Παρακάτω δίνεται η σύνταξη ενός SELECT ερωτήματος, για να ανακτήσουμε ένα entity.

                
                        SELECT ... FROM ...
                        [WHERE ...]
                        [GROUP BY ... [HAVING  ...]]
                        [ORDER BY ...]
                
            

Η σύνταξη των ερωτημάτων DELETE και UPDATE ερωτήματος δίνεται παρακάτω.

                
                        DELETE FROM ... [WHERE   ...]
                    
                        UPDATE ... SET ... [WHERE   ...]
                
            

Μπορούμε, να πραγματοποιήσουμε ερωτήματα χρησιμοποιώντας τη μέθοδο EntityManager.createQuery του EntityManager και την Query.executeUpdate. Παρακάτω δίνονται παραδείγματα SELECT, UPDATE και DELETE ερωτημάτων για την κλάση Entity Category.

                

                        public Category  findCategoryWithName(String name)  {

                          return em.createQuery(
                            "SELECT c FROM Category c WHERE c.name = :name")
                            .setParameter("name", "Vitamins")
                            .getResultList();
                        } 
                
            
                

                        public void  updateCategory(String name)  {

                          Query query = em.createQuery("UPDATE Category c SET c.name = :newName WHERE c.name = :oldName");
                          query.setParameter("newName", "Basket");
                          query.setParameter("oldName", "Football");
                          int rowsUpdated = query.executeUpdate();
                        } 
                    
                
            
                

                        public void  deleteCategory(String name)  {

                          Query query = em.createQuery("DELETE FROM Category c WHERE c.name = :name");
                          query.setParameter("name", "Basket");
                          int rowsDeleted = query.executeUpdate();
                        } 
                    
                
            
Criteria API

Το JPA Criteria API παρέχει έναν εναλλακτικό τρόπο, για να εκτελούμε δυναμικά JPA ερωτήματα, των οποίων η δομή είναι γνωστή, μόνα κατά την διάρκεια εκτέλεσης της εφαρμογής. Για παράδειγμα, μπορούμε να πραγματοποιήσουμε ένα δυναμικό ερώτημα, βασιζόμενοι στα φίλτρα αναζήτησης, τα οποία έχει επιλέξει ένας χρήστης.

Το CriteriaBuilder είναι η κύρια διεπαφή του Criteria API. Μπορούμε, να δημιουργήσουμε ένα στιγμιότυπο του CriteriaBuilder από τον EntityManager, χρησιμοποιώντας τη μέθοδο getCriteriaBuilder. Το CriteriaBuilder χρησιμοποιείται, για να δημιουργήσουμε CriteriaQuery αντικείμενα. Τα αντικείμενα CriteriaQuery ορίζουν ένα συγκεκριμένο ερώτημα, με το οποίο θα διασχίσουμε ένα ή περισσότερα entities.

Παρακάτω δίνεται ένα απλό ερώτημα σε JPQL

                
                        SELECT c FROM Category c 
                
            

Μπορούμε, να πραγματοποιήσουμε το ίδιο ερώτημα με το JPA Criteria API

                
                        CriteriaBuilder cb = em.getCriteriaBuilder();

                        CriteriaQuery<Category> q = cb.createQuery(Category.class);
                        Root<Category> c = q.from(Category.class);
                        q.select(c);
                        List<Category> categories = entityManager.createQuery(q).getResultList();
                
            

Query Root

Για κάθε CriteriaQuery αντικείμενο, το entity ρίζα του ερωτήματος, από το οποίο ξεκινάμε, να διασχίζουμε, λέγεται query root. Είναι αντίστοιχο με το FROM ενός JPQL ερωτήματος.

                
                        CriteriaQuery<Category> q = cb.createQuery(Category.class);
                        Root<Category> c = q.from(Category.class);
                
            

Μπορούμε, να δημιουργήσουμε συνθήκες χρησιμοποιώντας τη μέθοδο CriteriaQuery.where, η οποία περιορίζει τα αποτελέσματα ενός ερωτήματος στο αντικείμενο CriteriaQuery. Η μέθοδος where αντιστοιχεί με το WHERE ενός JPQL ερωτήματος.

Η μέθοδος where αποτιμά στιγμιότυπα της διεπαφής Expression, ώστε να περιορίσει τα αποτελέσματα, ανάλογα με τις συνθήκες των expressions. Το ακόλουθο ερώτημα χρησιμοποιεί τη μέθοδο Expression.isNull, για να βρει όλα τα προιόντα, στα οποία το πεδίο description είναι NULL.

                
                        CriteriaQuery<Product> cq = cb.createQuery(Product.class);
                        Root<Product> product = cq.from(Product.class);
                        cq.where(product.get("description).isNull());
                
            

Η διεπαφή CriteriaBuilder παρέχει επιπλέον μεθόδους, για τη δημιουργία expressions.

Keyword Sample
equal Ελέγχει, εάν δύο expressions είναι ίσα
notEqual Ελέγχει, εάν δύο expressions δεν είναι ίσα
gt Ελέγχει, εάν το πρώτο αριθμητικό expression είναι μεγαλύτερο του δεύτερου αριθμητικού expression
ge Ελέγχει, εάν το πρώτο αριθμητικό expression είναι μεγαλύτερο ή ίσο, σε σχέση με το δεύτερο αριθμητικού expression
lt Ελέγχει, εάν το πρώτο αριθμητικό expression είναι μικρότερο του δεύτερου αριθμητικού expression
le Ελέγχει, εάν το πρώτο αριθμητικό expression είναι μικρότερο ή ίσο, σε σχέση με το δεύτερο αριθμητικού expression
between Ελέγχει, εάν η τιμή του πρώτου expression, βρίσκεται ανάμεσα στη τιμή ένος δεύτερου και τρίτου expression
like Ελέγχει, εάν το expression ικανοποιεί ένα συγκεκριμένο pattern

Το παρακάτω ερώτημα επιστρέφει το προιον με όνομα Iphone-4.

                
                        CriteriaQuery<Product> cq = cb.createQuery(Product.class);
                        Root<Product> product = cq.from(Product.class);
                        cq.where(cb.equal(product.get( "name), "Iphone-4"));
                
            

Επίσης, η διεπαφή CriteriaBuilder παρέχει τις μεθόδους and, or και not, οι οποίες αντιστιχούν στους τελεστές AND, OR και NOT.

                
                        CriteriaQuery<Product> cq = cb.createQuery(Product.class);
                        Root<Product> product = cq.from(Product.class);
                        cq.where(cb.equal(product.get("id"), 3).or(cb.equal(product.get("name"), "Iphone-4")));
                
            

Τέλος, μπορούμε να κατατάξουμε τα αποτελέσματα, χρησιμοποιώντας CriteriaQuery.orderBy και δίνοντας ως παράμετρο ένα αντικείμενο Order. Παρακάτω δίνεται ένα παράδειγμα.

                
                        CriteriaQuery<Product> cq = cb.createQuery(Product.class);
                        Root<Product> product = cq.from(Product.class);
                        cq.select(product);
                        cq.orderBy(cb.asc(product.get("price)));
                
            
Πηγές