Utilizing Spring Data JPA to simplify the Data Access Infrastructure

Παρακατω θα μιλήσουμε για τις δυνατοτητες του Spring Data και πως μπορουμε να τις εφαρμοσουμε σε υπαρχοντες μηχανισμους επικοινωνιας με την Βαση Δεδομενων με την χρηση του JPA programming Interface.

Λιγα λογια για το Java Persistence API.
To spring Data ειναι ενα σχετικα νεο project που στοχο εχει να απλοποιησει την διαδικασια ανταλλαγης δεδομενων με την Βαση Δεδομενων που χρησιμοποιουμε, το λεγομενο data access layer δηλαδη.
Πιο συγκεκριμενα θα δουμε στην πραξη το subproject Spring Data JPA που απλοποιει σημαντικα τις διαδικασιες ανταλλαγης δεδομενων μας στο repository layer οταν χρησιμοποιουμε JPA annotated Pojos(Plain old Java classes). Πρακτικα δηλαδη ειναι οι γνωστες java κλασεις που υλοποιουν την συμβαση που οριζει το Java Persistence API (JPA). Η συμβαση αυτη οριζει οτι πρεπει να τοποθετησουμε ορισμενα annotations τοσο στην κλαση μας οσο και στις μεταβλητες που περιλαμβανει αυτη η κλαση.
Τα annotations αυτα στην ουσια υλοποιουν το γνωστο ORM(Object Relational Mapping) ή πιο απλα την συνδεση κλασεων και tables μιας βασης δεδομενων.
Ειναι δηλαδη τα annotations αυτα καποια extra πληροφορια, συμφωνη με τον τροπο που οριζει το JPA interface , και υλοποιησιμη απο τον μηχανισμο που επεκτεινει το JPA interface και επικοινωνει πραγματικα με την Βαση Δεδομενων. To πλεον διαδεδομενο Framework που υλοποιει το JPA interface και τα JPA annotations προκειμενου να συνδεσει τις κλασεις με τα tables της Βασης Δεδομενων ειναι το Hibernate.
Πριν περασουμε στις ευκολιες που παρεχει το Spring Data JPA Και στο πως αυτο μπορει να εφαρμοστει στο project μας, Θα δουμε αρχικα ενα πραγματικο παραδειγμα επικοινωνιας με την βαση με τον «παραδοσιακο τροπο».

Εστω η παρακατω JPA annotated class:

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

   @Id
   private Long productid;

   @Column(name="productdetails")
    private String productDetails;

   public Long getProductid() {
     return productid;
   }

   public void setProductid(Long productid) {
     this.productid = productid;
   }

   public String getProductDetails() {
     return productDetails;
   }
  
   public void setProductDetails(String productDetails) {
     this.productDetails = productDetails;
   }

   public Product() {
     super();
   }

   public Product(Long productid, String productDetails) {
      super();
      this.productid = productid;
      this.productDetails = productDetails;
   }
 }

Αφου δημιουργησαμε την κλαση μας, μενει να δημιουργησουμε το repository interface και το αντιστοιχο implementation του.

To Repository Tier το οποιο αναφερεται και ως DAO ( Data Access Object) ειναι εκεινο το layer στην εφαρμογη μας που περιλαμβανει ολες τις κλασεις που ειναι υπευθυνες για την επικοινωνια με το μεσο αποθηκευσης που διαθετει το εκαστοτε project(συνηθως μια Βαση Δεδομενων).

Στις Java Enterprise εφαρμογες, στο repository Layer τοποθετουμε ολους τους μηχανισμους που χρησιμοποιουν τον Entity Manager για να στειλουν και να λαβουν Δεδομενα απο την Βαση Δεδομενων.

O EntityManager ειναι ενα API που περιλαμβανει ολες τις μεθοδους για αποθηκευση και ανακτηση αντικειμενων απο το Persistence Context.

Το Persistence Context τελος ειναι ενα set απο οντοτητες (entities) που περιεχουν δεδομενα τα οποια προκειται να αποθευτουν στο μεσο αποθηκευσης που διαθετουμε στην εφαρμογη ( πχ Database).

Πιο συγκεκριμενα  οι οντοτητες αυτες μπορει να ειναι καποια αντικειμενα που δημιουργηθηκαν με βαση στοιχεια που εισηγαγε καποιος χρηστης σε ενα web view. Τα στοιχεια αυτα μπορει να αποτελουν χαρακτηριστικα ενος Προιοντος οποτε και δημιουργηθηκε το αντικειμενο product στην μνημη που ειναι κλασης Product την οποια και θα ορισουμε παρακατω.

Τωρα το αντικειμενο αυτο θα αποθηκευτει μεσω του Persistence Context στην Βαση Δεδομενων μας. Ο τροπος τωρα με τον οποιο επικοινωνουμε με το Persistence Context ειναι μεσω του Entity Manager που αναφεραμε παραπανω.

Το Persistence Context κατεχει δηλαδη ολες τις πληροφοριες για μια οντοτητα και τις διαφορες φασεις αυτης. Πχ μπορει η οντοτητα αυτη να ειναι στην μνημη με την μορφη αντικειμενου κλασης Product η να βρισκεται ηδη στην βαση Δεδομενων στον Πινακα Products.

Εδω ακριβως ειναι που ερχεται στο παιχνιδι το Spring Data το οποιο μας επιτρεπει να παραλειψουμε κοπιωδεις,επαναλαμβανομενες και error-prone διαδικασιες οπου προκειμενου να επικοινωνησουμε με το persistence context της εφαρμογης μας, πρεπει να γραψουμε κωδικα που να χειριζεται απευθειας τον entity Manager. Η κεντρικη ιδεα πισω απο το Spring Data ειναι οτι πολλες μεθοδοι που χρησιμοποιουν τον Entity Manager επαναλαμβανονται και το μονο που αλλαζει ειναι συνηθως το ονομα της εκαστοτε κλασης καθως και τα πεδια της.Ολα τα υπολοιπα μενουν ιδια. Οποτε δημιουργηθηκε ενας μηχανισμος που να αρκει να τοποθετουμε το ονομα της εκαστοτε κλασης και με λιγες ακομα προσθηκες μεθοδων να μπορουμε να εκτελεσουμε ολες τις βασικες διεργασιες επικοινωνιας με την Βαση Δεδομενων Οπως create, delete, update , delete οντοτητων.

Ας δουμε λοιπον ξεχωριστα την υλοποιηση με τον «παλαιο τροπο» που χρησιμοποιει απευθειας τον entity manager, και στην συνεχεια την υλοποιηση με το Spring Data JPA.

Υλοποιηση με απευθειας κληση του Entity Manager ,Repository and RepositoryImplementation

— To repository interface ειναι :

public interface ProductRepository
  {
        List findAll();
	Product findOne(Long id);
	Product save(Product product);
  }


— To repository implementation ειναι:

 

@Repository(“productRepository”) 
Public class ProductRepositoryImpl implements ProductRepository
{
	@PersistenceContext
        Private EntityManager em;

       public List findAll()
       {
	return em.createQuery("from Product").getResultList();
       }

      public Product save(Product product)
      {
	 Query query = em.createNativeQuery("insert into products (id,productdetails)  
         values (:id,:productdetails)",Product.class)
			.setParameter("id", product.getProductid())
			.setParameter("productdetails", product.getProductDetails());
			query.executeUpdate();
	
	em.flush();
	return product;
      }

      public Product findOne(Long id) {

	Query query = em.createNativeQuery("select * from products where id = :id",    
        Product.class).setParameter("id",id);

	return (Product)query.getSingleResult();
       }
}

Υλοποιηση με το Spring Data JPA

Αρχικα πρεπει να εχουμε δωσει το καταλληλο dependency στο pom.xml αρχειο της εφαρμογης μας:

  
          
	        org.springframework.data
	        spring-data-jpa
	        1.4.3.RELEASE
	 

Στην συνεχεια πηγαινουμε στο Repository interface και συμπληρωνουμε τα παρακατω:

 

Public interface ProductRepository extends JpaRepository
{

}

Πρεπει δηλαδη το interface μας να επεκτεινει(extends) το JpaRepository.
Επισης , Στο JpaRepository δινουμε τις εξης 2 τιμες: την κλαση στην οποια
αναφερεται το Repository καθως και τον τυπο του «κλειδιου»(@annotation id)
Της κλασης που αναφερεται το repository.

Οριζοντας εδω την κλαση και το κλειδι της, δινουμε την δυνατοτητα στους μηχανισμους του Spring Data να επικοινωνησουν μεσω του entity manager με το Persistence Context που κατεχει οπως αναφεραμε προηγουμενως πληροφοριες για την εκαστοτε οντοτητα και την αποθηκευση της στην Βαση Δεδομενων.

Αυτο ηταν!

Τωρα πλεον μπορουμε να παραλειψουμε εντελως το implementation του Repository καθως αυτο θα το αναλαβει το Spring Data.

Πηγαινοντας λοιπον στις λεπτομερειες του JPA repository, θα δουμε οτι αυτο περιεχει διαφορες χρησιμες κλασεις για save, update και delete τις οποιες μπορουμε να καλεσουμε απευθειας μεσα απο το Service Layer της εφαρμογης μας.

Για να κατανοησουμε πληρως την χρηστικοτητα του Spring Data, αρκει να ανεβουμε ενα επιπεδο πανω στην ιεραρχεια της εφαρμογης μας και να δουμε το Service Tier που περιλαμβανει το business logic της εφαρμογης και καλει τις μεθοδους του Repository.

Ετσι για παραδειγμα το ProductService interface μας θα ηταν:

 
public interface ProductService {

public List findAll();
public Page findPaginated(Pageable pageable);
public Product findOne(Long productId);
public List findByCategory(Category category);
}

 

 

Ενω η αντιστοιχη κλαση που υλοποιει το παραπανω interface και παραλληλα καλει με χρηση του μηχανισμου @Autowired του Spring Framework, το αντιστοιχο ProductRepository , θα ηταν:

 

@Service
public class ProductServiceImpl implements ProductService {


    @Autowired
    ProductRepository productRepository;

    @Override
     public List findAll() {
         return productRepository.findAll();
     }
    @Override
     public Page findPaginated(Pageable pageable) {
         return productRepository.findAll(pageable);
     }
    @Override
     public Product findOne(Long productId) {
       return productRepository.findOne(productId);
     }
}

Βλεπουμε λοιπον οτι πλεον στο service μας μπορουμε να καλεσουμε κλασσικες μεθοδους επικοινωνιας με την Βαση Δεδομενων μας(save, update, delete) καλοντας το καταλληλο repository, παρολο που στο ιδιο το repository δεν εχουμε ορισει τιποτα!

Στο παρακατω λινκ μπορειτε να βρειτε πολλες διαθεσιμες μεθοδους που γινονται προσβασιμες μεσω του Spring Data repository interface.

http://docs.spring.io/spring-data/jpa/docs/current/reference/html/

Φυσικα με τον παραπανω τροπο εχουμε προσβαση μονο σε μεθοδους create , update, read , delete που αφορουν μια οντοτητα, και δεν εχουμε υλοποιηση λιγο πιο συνθετων queries που μπορει να περιλαμβανουν την αναζητηση μιας οντοτητας με βαση 2 πεδια της.

Εδω λοιπον αρκει να προσθεσουμε την αντιστοιχη μεθοδο στο ProductRepository interface και να αφησουμε το Spring Data να συμπληρωσει το implementation.

Ετσι προκυπτει  το εξης ProductRepository interface:

 
 Public interface ProductRepository extends JpaRepository<Product,Long>
    {
      Public List findByProductIdAndProductDetails(Long productid, String     
       productDetails);
   }

Τελος , ας δουμε την διαφοροποιηση στο configuration στο αρχειο .xml οπου

Οριζουμε τα σχετικα με το Persistence Context και τον Entity Manager:



– Προσθηκη του xml tag:


 

στο οποιο οριζουμε ουσιαστικα το package στο οποιο γινεται χρηση του Spring Data . Φυσικα μπορουμε να εχουμε περισσοτερα απο ενα jpa repositories.

Passionate Archer, Runner, Linux lover and JAVA Geek! That's about everything! Alexius Dionysius Diakogiannis is a Senior Java Solutions Architect and Squad Lead at the European Investment Bank. He has over 20 years of experience in Java/JEE development, with a strong focus on enterprise architecture, security and performance optimization. He is proficient in a wide range of technologies, including Spring, Hibernate and JakartaEE. Alexius is a certified Scrum Master and is passionate about agile development. He is also an experienced trainer and speaker, and has given presentations at a number of conferences and meetups. In his current role, Alexius is responsible for leading a team of developers in the development of mission-critical applications. He is also responsible for designing and implementing the architecture for these applications, focusing on performance optimization and security.