Spring Data JPA

    The article will describe the use of Spring Data.

    Spring Data is an additional convenient mechanism for interacting with database entities, organizing them in a repository, extracting data, changing, in some cases, it will be enough to declare the interface and method in it, without implementation.

    Content:

    1. Spring repository
    2. Query methods from method name
    3. Configuration and configuration
    4. Special parameter processing
    5. Custom Repository Implementations
    6. User Base Repository
    7. Query Methods - Query


    1. Spring Repository


    The basic concept in Spring Data is a repository. These are several interfaces that use JPA Entity to interact with. For example, the interface
    public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID>
    provides basic operations for searching, saving, deleting data (CRUD operations)

    T save(T entity);
    Optional findById(ID primaryKey);
    void delete(T entity);
    

    and other operations.

    There are other abstractions, such as PagingAndSortingRepository.

    Those. If the list that the interface provides is sufficient for interacting with the entity, then you can directly extend the basic interface for your entity, supplement it with your own query methods and perform operations. Now I will briefly show the steps that are needed for the simplest case (without being distracted by the configuration, ORM, database).

    1. Create an entity

    @Entity@Table(name = "EMPLOYEES")
    publicclassEmployees{
        private Long employeeId;
        private String firstName;
        private String lastName;
        private String email;
        // . . . 

    2. Inherit from one of the Spring Data interfaces, for example from CrudRepository

    @RepositorypublicinterfaceCustomizedEmployeesCrudRepositoryextendsCrudRepository<Employees, Long>
    

    3. Use in the client (service) a new interface for data operations

    @ServicepublicclassEmployeesDataService{
     @Autowiredprivate CustomizedEmployeesCrudRepository employeesCrudRepository;
      @TransactionalpublicvoidtestEmployeesCrudRepository(){
    	Optional<Employees> employeesOptional = employeesCrudRepository.findById(127L);
    	//....
        }	

    Here I used the findById ready method . Those. so easily and quickly, without implementation, we get a ready list of operations from CrudRepository:

    S save(S var1);
        Iterable<S> saveAll(Iterable<S> var1);
        Optional<T> findById(ID var1);
        booleanexistsById(ID var1);
        Iterable<T> findAll();
        Iterable<T> findAllById(Iterable<ID> var1);
        longcount();
        voiddeleteById(ID var1);
        voiddelete(T var1);
        voiddeleteAll(Iterable<? extends T> var1);
        voiddeleteAll();
    

    It is clear that this list is most likely not enough to interact with the entity, and here you can expand your interface with additional query methods.

    2. Query methods from method name


    Requests to the entity can be built directly from the name of the method. For this, the mechanism of prefixes find ... By, read ... By, query ... By, count ... By, and get ... By is used, further from the method prefix begins the analysis of the rest. The introductory sentence may contain additional expressions, for example, Distinct. Further, the first By acts as a separator to indicate the beginning of the actual criteria. You can define conditions for properties of entities and combine them with And and Or. Examples

    @RepositorypublicinterfaceCustomizedEmployeesCrudRepositoryextendsCrudRepository<Employees, Long> {
        // искать по полям firstName And LastNameOptional<Employees> findByFirstNameAndLastName(String firstName, String lastName);
        // найти первые 5 по FirstName начинающихся с символов и сортировать по FirstName List<Employees> findFirst5ByFirstNameStartsWithOrderByFirstName(String firstNameStartsWith);
    

    The documentation defines the entire list, and the rules for writing the method. The result can be an entity T, Optional, List, Stream. In the development environment, such as Idea, there is a hint for writing query methods.

    image
    It is enough to define a method in the same way, without implementation, and Spring will prepare a request for the entity.

    @SpringBootTestpublicclassDemoSpringDataApplicationTests{
    @Autowiredprivate CustomizedEmployeesCrudRepository employeesCrudRepository;
    @Test@TransactionalpublicvoidtestFindByFirstNameAndLastName(){
    		Optional<Employees> employeesOptional = employeesCrudRepository.findByFirstNameAndLastName("Alex", "Ivanov");
    

    3. Configuration and configuration


    The whole project is available on github
    github DemoSpringData.

    Here I will just touch on some features.

    In context.xml, the transactionManager, dataSource, and entityManagerFactory beans are defined. It is important to indicate in it also

    <jpa:repositoriesbase-package="com.example.demoSpringData.repositories"/>

    path where repositories are defined.

    EntityManagerFactory is configured to work with the Hibernate ORM, and it, in turn, with the Oracle XE database, other options are possible here, in the context.xml you can see it all. The pom file has all the dependencies.

    4. Special parameter processing


    In the query methods, their parameters can use the special parameters Pageable, Sort, as well as the restrictions Top and First.

    For example, this is how you can take the second page (index with -0), the size of three elements and sorting by firstName, previously specifying the Pageable parameter in the repository method, the criteria from the method name - “Search by FirstName starting from%„

    @RepositorypublicinterfaceCustomizedEmployeesCrudRepositoryextendsCrudRepository<Employees, Long> {
        List<Employees> findByFirstNameStartsWith(String firstNameStartsWith, Pageable page);
    //....
    }
    // пример вызова@Test@TransactionalpublicvoidtestFindByFirstNameStartsWithOrderByFirstNamePage(){
    	List<Employees> list = employeesCrudRepository
    	.findByFirstNameStartsWith("A", PageRequest.of(1,3, Sort.by("firstName")));
    	list.forEach(e -> System.out.println(e.getFirstName() + " " +e.getLastName()));
    }
    

    5. Custom implementations for the repository


    Suppose that in the repository you need a method that cannot be described by the method name, then you can implement it using your interface and implementing class. In the example below, I’ll add to the repository a method for getting employees with maximum pay.

    I declare the interface

    publicinterfaceCustomizedEmployees<T> {
        List<T> getEmployeesMaxSalary();
    }
    

    I implement the interface. With the help of HQL (SQL) I get employees with maximum pay, other implementations are possible.

    publicclassCustomizedEmployeesImplimplementsCustomizedEmployees{
        @PersistenceContextprivate EntityManager em;
        @Overridepublic List getEmployeesMaxSalary(){
            return em.createQuery("from Employees where salary = (select max(salary) from Employees )", Employees.class)
                    .getResultList();
        }
    }
    

    As well as expanding the Crud Repository Employees also CustomizedEmployees.

    @RepositorypublicinterfaceCustomizedEmployeesCrudRepositoryextendsCrudRepository<Employees, Long>, CustomizedEmployees<Employees> 
    

    There is one important feature here. The class that implements the interface, must end (postfix) with Impl , or you must put your postfix in the configuration

    <repositoriesbase-package="com.repository"repository-impl-postfix="MyPostfix" />

    We check the work of this method through the repository.

    publicclassDemoSpringDataApplicationTests{
    @Autowiredprivate CustomizedEmployeesCrudRepository employeesCrudRepository;
    @Test@TransactionalpublicvoidtestMaxSalaryEmployees(){
    	List<Employees> employees = employeesCrudRepository.getEmployeesMaxSalary();
    	employees.stream()
    	.forEach(e -> System.out.println(e.getFirstName() + " " + e.getLastName() + " " + e.getSalary()));
    }
    

    Another case when I need to change the behavior of an already existing method in the Spring interface, for example, delete in CrudRepository, I need, instead of deleting from the database, a sign of deletion be set. The technique is exactly the same. Below is an example:

    publicinterfaceCustomizedEmployees<T> {
        voiddelete(T entity);
        // ...
    }
    // Имплементация CustomizedEmployeespublicclassCustomizedEmployeesImplimplementsCustomizedEmployees{
        @PersistenceContextprivate EntityManager em;
        @Transactional@Overridepublicvoiddelete(Object entity){
            Employees employees = (Employees) entity;
            employees.setDeleted(true);
            em.persist(employees);
        }
    

    Now if you call delete in the employeesCrudRepository , then the object will only be marked as deleted.

    6. Custom Base Repository


    In the previous example, I showed how to override delete in the Crud entity repository, but if you need to do this for all project entities, you can make your own interface for each one not so ... then in Spring data you can configure your base repository. To do this:
    Declares an interface and a method for overriding it (or common for all project entities). Here I have introduced BaseEntity for all my entities (this is not necessary), for the convenience of calling common methods, its methods coincide with the methods of the entity.

    publicinterfaceBaseEntity{
        Boolean getDeleted();
        voidsetDeleted(Boolean deleted);
    }
    // Сущность Employees @Entity@Table(name = "EMPLOYEES")
    publicclassEmployeesimplementsBaseEntity{
      private Boolean deleted;
        @Overridepublic Boolean getDeleted(){
            return deleted;
        }
        @OverridepublicvoidsetDeleted(Boolean deleted){
            this.deleted = deleted;
        }
    // Базовый пользовательский интерфейс@NoRepositoryBeanpublicinterfaceBaseRepository <TextendsBaseEntity, IDextendsSerializable>
            extendsJpaRepository<T, ID> {
        voiddelete(T entity);
    }
    //Базовый пользовательский класс имплементирующий BaseRepositorypublicclassBaseRepositoryImpl <TextendsBaseEntity, IDextendsSerializable>
            extendsSimpleJpaRepository<T, ID>
            implementsBaseRepository<T, ID> {
        privatefinal EntityManager entityManager;
        publicBaseRepositoryImpl(JpaEntityInformation<T, ?> entityInformation,
                         EntityManager entityManager){
            super(entityInformation, entityManager);
            this.entityManager = entityManager;
        }
        @Transactional@Overridepublicvoiddelete(BaseEntity entity){
            entity.setDeleted(true);
            entityManager.persist(entity);
        }
    }
    

    In the configuration, you must specify this base repository, it will be common to all project repositories.

    <jpa:repositoriesbase-package="com.example.demoSpringData.repositories"base-class="com.example.demoSpringData.BaseRepositoryImpl"/>

    Now the Employees Repository (and others) need to be expanded from BaseRepository and already be used in the client.

    publicinterfaceEmployeesBaseRepositoryextendsBaseRepository <Employees, Long> {
      // ...
    }
    

    I check the work of EmployeesBaseRepository

    publicclassDemoSpringDataApplicationTests{
     @Resourceprivate EmployeesBaseRepository employeesBaseRepository;
    @Test@Transactional@CommitpublicvoidtestBaseRepository(){
    	Employees employees = new Employees();
    	employees.setLastName("Ivanov");
            // Query by Example  (QBE)
    	Example<Employees> example = Example.of(employees);
    	Optional<Employees> employeesOptional = employeesBaseRepository.findOne(example);
    	employeesOptional.ifPresent(employeesBaseRepository::delete);
    }
    

    Now, as before, the object will be marked as deleted, and this will be done for all entities that extend the BaseRepository interface. In the example, the search method was used - Query by Example (QBE) , I will not describe it here, it is clear from the example what it does, simple and convenient.

    7. Query Methods - Query


    Earlier, I wrote that if you need a specific method or its implementation that cannot be described through the name of the method, then this can be done through some Customized Interface (CustomizedEmployees) and make the implementation of the calculation. And you can go the other way, by specifying the query (HQL or SQL), how to calculate this function.
    For my example with getEmployeesMaxSalary, this implementation is even simpler. I will complicate it with the salary input parameter. Those. it is enough to declare a method and a calculation request in the interface.

    @RepositorypublicinterfaceCustomizedEmployeesCrudRepositoryextendsCrudRepository<Employees, Long>, CustomizedEmployees<Employees> {
        @Query("select e from Employees e where e.salary > :salary")
        List<Employees> findEmployeesWithMoreThanSalary(@Param("salary") Long salary, Sort sort);
        // ...
    }
    

    Checking

    @Test@TransactionalpublicvoidtestFindEmployeesWithMoreThanSalary(){
    	List<Employees> employees = employeesCrudRepository.findEmployeesWithMoreThanSalary(10000L, Sort.by("lastName"));
    

    I will only mention that requests can also be modifying, for this purpose, the @Modifying annotation is added to them .

    @Modifying@Query("update Employees e set e.firstName = ?1 where e.employeeId = ?2")
    intsetFirstnameFor(String firstName, String employeeId);
    

    Another of the remarkable features of Query annotations is the substitution of the domain type of an entity into a query using the pattern # {# entityName} , through SpEL expressions.

    So for example, in my hypothetical example, when I need to have the attribute “deleted” for all entities, I will do a basic interface with the method of getting a list of objects with the attribute “deleted” or “active”

    @NoRepositoryBeanpublicinterfaceParentEntityRepository<T> extendsRepository<T, Long> {
        @Query("select t from #{#entityName} t where t.deleted = ?1")
        List<T> findMarked(Boolean deleted);
    }
    

    Further all repositories for entities can be expanded from it. Interfaces that are not repositories, but are located in the "base-package" configuration folder, you must annotate @NoRepositoryBean.

    Employees repository

    @RepositorypublicinterfaceEmployeesEntityRepositoryextendsParentEntityRepository <Employees> {
    }
    

    Now when the query is executed, the name of the T entity for the specific repository that will extend the ParentEntityRepository, in this case, Employees, will be substituted into the body of the query .

    Check

    @SpringBootTestpublicclassDemoSpringDataApplicationTests{
    @Autowiredprivate EmployeesEntityRepository employeesEntityRepository;
    @Test@TransactionalpublicvoidtestEntityName(){
    	List<Employees> employeesMarked = employeesEntityRepository.findMarked(true);
            // ...

    Materials
    Spring Data JPA - Reference Documentation
    Project on github .

    Also popular now: