Spring Data JPA

Στις περισσότερες enterprise εφαρμογές, η ανάκτηση πληροφοριών από την βάση δεδομένων είναι συχνή απαίτηση. H Java παρέχει το JDBC API, το οποίο μας επιτρέπει, να εκτελούμε SQL εντολές στη βάση δεδομένων. Όμως όταν χρησιμοποιούμε το JDBC, πρέπει εμείς, να διαχειριστούμε τους πόρους της βάσης δεδομένων και τα Exceptions, που προκύπτουν κατά την εκτέλεση του κώδικα. Επίσης, ο κώδικας είναι αρκετά φλύαρος. Παρακάτω δίνεται ο κώδικας, που εκτελεί ένα SELECT στη βάση δεδομένων:

                
                        public class JDBCExample  {

                           //Όνομα JDBC driver και το url της βάσης δεδομένων
                           static final  String JDBC_DRIVER = "com.mysql.jdbc.Driver";
                           static final  String DB_URL = "jdbc:mysql://localhost/database";

                           //Όνομα χρήστη και κωδικός
                           static final  String USER = "username";
                           static final  String PASS = "password";

                           public static void main(String[] args) {

                           Connection conn = null;
                           Statement stmt = null;

                           try{

                             Class.forName("com.mysql.jdbc.Driver");
                             conn = DriverManager.getConnection(DB_URL, USER, PASS);
                             stmt = conn.createStatement();
                             String sql = "SELECT id, name FROM Categories";
                             ResultSet rs = stmt.executeQuery(sql);
                             while(rs.next()){
                               int id  = rs.getInt("id");
                               String name = rs.getString("name");
                               //Μετά κάνουμε ότι θέλουμε με τα αποτελέσματα
                             }
                             rs.close();

                           }catch(SQLException se){
                             se.printStackTrace();
                           }catch(Exception  e){
                             e.printStackTrace();
                           }finally{
                             try{

                                if(stmt != null)
                                  stmt.close();

                                if(conn != null)
                                  conn.close();

                             }catch(SQLException se){
                               se.printStackTrace();
                             }
                           }
                    
                          }

                        } 
                
            
JPA

Εναλλακτικά, μπορούμε να χρησιμοποιήσουμε το 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 στη βάση δεδομένων:

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

                        } 
                
            
                
                        @Repository
                        public class CategoryRepositoryImpl implements CateroryRepository{

                           @PersistenceContext
                           private  EntityManager entityManager;
                    
                           public void create(Category category) {
                             entityManager.persist(category);
                           }

                        } 
                
            

Η μέθοδος create του repository είναι πολύ μικρότερη και δεν κάνουμε έλεγχο για exceptions. Παρ' όλα αυτά, πρέπει να γράψουμε μια μέθοδο για κάθε CRUD λειτουργεία, που θέλουμε, να πραγματοποιήσουμε στη βάση.

Data Access Object - Repositories

Ο σκοπός του Data Access Object(DAO) pattern είναι ο διαχωρισμός του data access logic, από το business logic και το presentation logic. Αυτό το pattern προτείνει, το data access logic να ενθυλακώνεται σε ανεξάρτητα modules, που λέγονται data access objects.

Το Spring υποστηρίζει το Data Access Object, καθιστώντας εύκολο, να δουλεύουμε με διαφορετικές τεχνολογίες data access(JDBC, Hibernate ή JPA) με έναν συνεπή τρόπο. Mεταφράζει συγκεριμένα τεχνολογικά exceptions, όπως SQLException, στην δικιά του ιεραρχία exception κλάσεων, τα οποία έχουν το DataAccessException ως exception ρίζα. Αυτά τα exceptions περικλείουν το αρχικό exception.

Για να δηλώσουμε ότι μια κλάση είναι DAO ή repository και αυτή να παρέχει μετάφραση των exceptions, χρησιμοποιούμε το annotation @Repository.

Παράδειγμα Repository

                
                        @Repository
                        public class JpaMovieFinder implements MovieFinder{

                           @PersistenceContext
                           private EntityManager entityManager;

                           // ...

                        } 
                
            
Spring Data Jpa

Το Spring Data JPA δεν είναι ένας JPA provider, αλλά ένα specification. Είναι ένα framework, το οποίο παρέχει ένα επιπλέον επίπεδο αφαίρεσης πάνω από τον JPA provider. Ο στόχος της αφαίρεσης Spring Data repository είναι, να μειώσει σημαντικά τον boilerplate κώδικα, που απαιτείται για να δημιουργήσουμε το επίπεδο data access.

Το βασικό repository, που παρέχει το Spring Data είναι το CrudRepository. Αυτό παρέχει τις βασικές λειτουργίες CRUD(Create, Read, Update, Delete).

                
                        public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID>  {

                           // Αποθηκεύει το συγκεκριμένο entity.
                           <S extends T> S save(S entity); 

                           // Επιστρέφει το entity, με αυτό το ID.
                           Optional<T> findById(ID primaryKey);

                           // Επιστρέφει όλα τα entities.
                           Iterable<T> findAll(); 

                           // Επιστρέφει τον αριθμό των entities.
                           long count(); 

                           // Διαγράφει το συγκεκριμένο entity.
                           void delete(T entity);

                           // Επιστρέφει αν το entity, με το ID αυτό υπάρχει.
                           boolean existsById(ID primaryKey);

                        } 
                
            

Το Spring Data JPA παρέχει τις υλοποιήσεις των μεθόδων για εμάς.

                
                        @Repository
                        public interface ProductRepository  extends CrudRepository<Product, Integer>  {

                        } 
                
            

Εάν θέλουμε να βρούμε ένα προιόν, ο κώδικας δίνεται παρακάτω:

                
                        @Autowired
                        private ProductRepository  productRepository;

                        Product product = productRepository.findById(4);
                
            

Για να αποθηκεύσουμε ένα προιόν, ο κώδικας δίνεται παρακάτω:

                
                        Product product = new Product();
                        product.setName("name");
                        product.setPrice(25);
                        productRepository.save(product);
                
            
JPA Repositories

Τα Spring Data JPA repositories είναι default Spring beans. Είναι singletons και αρχικοποιούνται γρήγορα. Κάνουν extend τα CrudRepository<T, ID> και PagingAndSortingRepository<T, ID>, οπότε κληρονομούν την λειτουργικότητα τους.

Για να μπορέσουμε, να χρησιμοποιήσουμε JPA Repositories, χρειάζεται XML ή Java Configuration. Παρακάτω, δίνεται το Java configuration.

                
                        @Configuration
                        @EnableJpaRepositories
                        @EnableTransactionManagement
                        public class ApplicationConfig {

                           @Bean
                           public DataSource dataSource(){
                             EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
                             return builder.setType(EmbeddedDatabaseType.HSQL).build();
                           }

                           @Bean
                           public DataSource dataSource(){
                             HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
                             vendorAdapter.setGenerateDdl(true);
                             LocalContainerEntityManagerFactoryBean factory  = new LocalContainerEntityManagerFactoryBean();
                             factory.setJpaVendorAdapter(vendorAdapter);
                             factory.setPackagesToScan("com.acme.repositories");
                             factory.setDataSource(dataSource());
                             return factory;
                           }

                           @Bean
                           public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory){
                             JpaTransactionManager txManager = new JpaTransactionManager();
                             txManager.setEntityManagerFactory(entityManagerFactory);
                             return txManager;
                           }

                        } 
                
            

Το παραπάνω configuration ρυθμίζει μια embedded HSQL βάση δεδομένων, χρησιμοποιώντας το EmbeddedDatabaseBuilder API του spring-jdbc. Το Spring Data δημιουργεί ένα EntityManagerFactory και χρησιμοποιεί το Hibernate ως persistence provider. Το 3ο bean που δημιουργείται, είναι το JpaTransactionManager. Τέλος, τα Spring Data JPA repositories ενεργοποιούνται, χρησιμοποιώντας το annotation @EnableJpaRepositories.

Δημιουργία ερωτημάτων

Μπορούμε, να ορίσουμε ένα ερώτημα ως String ή μέσω του ονόματος της μεθόδου.

Δημιουργία ερωτημάτων μέσω του ονόματος της μεθόδου

                  
                        public interface  UserRepository extends JpaRepository<User, Long>{

                              List<User> findByEmailAddressAndLastname(String emailAddress, String lastname)
                        } 
                
            

Δημιουργούμε ένα ερώτημα, χρησιμοποιώντας το JPA criteria API και αύτο μεταφράζεται στο εξής ερώτημα : select u from User where u.emailAddress = ?1 and u.lastname = ?2. Το JPA ελέγχει και διασχίζει τα properties.

Keyword Sample JPQL snippet
And findByLastnameAndFirstname … where x.lastname = ?1 and x.firstname = ?2
Or findByLastnameOrFirstname … where x.lastname = ?1 or x.firstname = ?2
Is,Equals findByFirstname,findByFirstnameIs,findByFirstnameEquals … where x.firstname = ?1
Between findByStartDateBetween … where x.startDate between ?1 and ?2
LessThan findByAgeLessThan … where x.age < ?1
LessThanEqual findByAgeLessThanEqual … where x.age <= ?1
GreaterThan findByAgeGreaterThan … where x.age > ?1
GreaterThanEqual findByAgeGreaterThanEqual … where x.age >= ?1
IsNull findByAgeIsNull … where x.age is null
IsNotNull,NotNull findByAge(Is)NotNull … where x.age not null
Like findByFirstnameLike … where x.firstname like ?1
OrderBy findByAgeOrderByLastnameDesc … where x.age = ?1 order by x.lastname desc
In findByAgeIn(Collection<Age> ages) … where x.age in ?1
True findByActiveTrue() … where x.active = true
False findByActiveFalse() … where x.active = false

Δημιουργία ερωτημάτων με το annotation @Query

Μπορούμε, να χρησιμοποιήσουμε το annotation @Query στην αντίστοιχη μέθοδο, δηλώνοντας το ερώτημα, που θέλουμε να εκτελεστεί.

                  
                        public interface  UserRepository extends JpaRepository<User, Long>{

                          @Query("select u from User u where u.emailAddress = ?1")
                          User findByEmailAddress(String emailAddress);
                        } 
                
            

Εάν το ερώτημα ενημερώνει ή προσθέτει γραμμές σε κάποιο πίνακα της βάσης, τότε χρησιμοποιούμε το annotation @Modifying.

                  
                          @Modifying
                          @Query("update User u set u.firstname = ?1 where u.lastname = ?2")
                          int setFixedFirstnameFor(String firstname, String lastname);
                
            

Επίσης, μπορούμε να χρησιμοποιήσουμε το annotation @Param, για να δώσουμε όνομα σε μια παράμετρο μεθόδου και να κάνουμε bind το όνομα στο ερώτημα

                  
                        public interface  UserRepository extends JpaRepository<User, Long>{

                          @Query("select u from User u where u.firstname = :firstname or u.lastname = :lastname"")
                          User findByEmailAddress(@Param("lastname") String lastname,@Param("firstname") String firstname);
                        } 
                
            

Τέλος, το annotation @Query μας δίνει τη δυνατότητα, να πραγματοποιήσουμε και native ερωτήματα, θέτοντας τη σημαία nativeQuery σε true.

                  
                        public interface  UserRepository extends JpaRepository<User, Long>{

                          @Query( value = "SELECT * FROM USERS WHERE EMAIL_ADDRESS = ?1", nativeQuery = true)
                          User findByEmailAddress(String emailAddress);
                        } 
                
            
Πηγές