Статьи

Добавление пользовательских функций в репозиторий данных Spring

Spring Data довольно удобен и ускоряет разработку, избегая стандартного кода. Однако в некоторых случаях запросов аннотации недостаточно для пользовательских функций, которые вы, возможно, захотите достичь.

Поэтому данные Spring позволяют нам добавлять собственные методы в репозиторий данных Spring. Я буду использовать ту же структуру проекта из предыдущего поста в блоге.

У нас есть организация под названием Сотрудник

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package com.gkatzioura.springdata.jpa.persistence.entity;
 
import javax.persistence.*;
 
/**
 * Created by gkatzioura on 6/2/16.
 */
@Entity
@Table(name = "employee", schema="spring_data_jpa_example")
public class Employee {
 
    @Id
    @Column(name = "id")
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
 
    @Column(name = "firstname")
    private String firstName;
 
    @Column(name = "lastname")
    private String lastname;
 
    @Column(name = "email")
    private String email;
 
    @Column(name = "age")
    private Integer age;
 
    @Column(name = "salary")
    private Integer salary;
 
    public Long getId() {
        return id;
    }
 
    public void setId(Long id) {
        this.id = id;
    }
 
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastname() {
        return lastname;
    }
 
    public void setLastname(String lastname) {
        this.lastname = lastname;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }
 
    public Integer getAge() {
        return age;
    }
 
    public void setAge(Integer age) {
        this.age = age;
    }
 
    public Integer getSalary() {
        return salary;
    }
 
    public void setSalary(Integer salary) {
        this.salary = salary;
    }
}

И хранилище данных Spring

01
02
03
04
05
06
07
08
09
10
11
12
13
package com.gkatzioura.springdata.jpa.persistence.repository;
 
import com.gkatzioura.springdata.jpa.persistence.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
/**
 * Created by gkatzioura on 6/2/16.
 */
@Repository
public interface EmployeeRepository extends JpaRepository<Employee,Long>{
 
}

Предположим, что мы хотим добавить некоторые пользовательские функции sql, например, запрос с помощью оператора LIKE и соединение с таблицей, которая не отображается как сущность.

Это только для демонстрационных целей. Для вашего проекта у вас может быть лучшая схема. Плюс пружинные данные поставляются с готовой функциональностью для похожих операторов, посмотрите EndingWith, Conisting, StartingWith .

Мы создадим бонусную таблицу и добавим ссылку на таблицу сотрудников.

01
02
03
04
05
06
07
08
09
10
11
12
13
set schema 'spring_data_jpa_example';
 
create table bonus(
    id serial primary key,
    employee_id integer,
    amount real,
    foreign key (employee_id) references employee (id),
    unique (employee_id)
    );
 
insert into bonus
( employee_id, amount)
VALUES(1, 100);

SQL-запрос, который мы хотим реализовать, будет запрашивать сотрудников, чье имя начинается с указанного текста и бонуса, превышающего определенную сумму. В jdbc мы должны передать нашу переменную конкатенированную с символом «%».

Так что нам нужен собственный запрос jpa, как этот

1
2
3
4
5
6
Query query = entityManager.createNativeQuery("select e.* from spring_data_jpa_example.bonus b, spring_data_jpa_example.employee e\n" +
                "where e.id = b.employee_id " +
                "and e.firstname LIKE ? " +
                "and b.amount> ? ", Employee.class);
        query.setParameter(1, firstName + "%");
        query.setParameter(2, bonusAmount);

Чтобы добавить эту функциональность в наш репозиторий данных Spring, мы должны добавить интерфейс. Наш интерфейс должен следовать соглашению об именах $ {Original Repository name} Custom. Поэтому интерфейс, описывающий наши пользовательские функции, должен быть

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package com.gkatzioura.springdata.jpa.persistence.repository;
 
import com.gkatzioura.springdata.jpa.persistence.entity.Employee;
 
import java.util.List;
 
/**
 * Created by gkatzioura on 6/3/16.
 */
public interface EmployeeRepositoryCustom {
 
    List<Employee> getFirstNamesLikeAndBonusBigger(String firstName, Double bonusAmount);
 
}

И реализация должна быть

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.gkatzioura.springdata.jpa.persistence.repository;
 
import com.gkatzioura.springdata.jpa.persistence.entity.Employee;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import java.util.List;
 
/**
 * Created by gkatzioura on 6/3/16.
 */
@Repository
@Transactional(readOnly = true)
public class EmployeeRepositoryImpl implements EmployeeRepositoryCustom {
 
    @PersistenceContext
    EntityManager entityManager;
 
    @Override
    public List<Employee> getFirstNamesLikeAndBonusBigger(String firstName, Double bonusAmount) {
        Query query = entityManager.createNativeQuery("select e.* from spring_data_jpa_example.bonus b, spring_data_jpa_example.employee e\n" +
                "where e.id = b.employee_id " +
                "and e.firstname LIKE ? " +
                "and b.amount> ? ", Employee.class);
        query.setParameter(1, firstName + "%");
        query.setParameter(2, bonusAmount);
 
        return query.getResultList();
    }
}

И мы должны изменить наш исходный репозиторий данных Spring, чтобы наследовать пользовательские функции.

01
02
03
04
05
06
07
08
09
10
11
12
package com.gkatzioura.springdata.jpa.persistence.repository;
 
import com.gkatzioura.springdata.jpa.persistence.entity.Employee;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
 
/**
 * Created by gkatzioura on 6/2/16.
 */
@Repository
public interface EmployeeRepository extends JpaRepository<Employee,Long>, EmployeeRepositoryCustom {
}

Похоже, хороший способ композиции. Теперь давайте добавим метод в контроллер, который будет вызывать этот пользовательский метод

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package com.gkatzioura.springdata.jpa.controller;
 
        import com.gkatzioura.springdata.jpa.persistence.entity.Employee;
        import com.gkatzioura.springdata.jpa.persistence.repository.EmployeeRepository;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.web.bind.annotation.RequestMapping;
        import org.springframework.web.bind.annotation.RequestParam;
        import org.springframework.web.bind.annotation.RestController;
 
        import java.util.List;
 
/**
 * Created by gkatzioura on 6/2/16.
 */
@RestController
public class TestController {
 
    @Autowired
    private EmployeeRepository employeeRepository;
 
    @RequestMapping("/employee")
    public List<Employee> getTest() {
 
        return employeeRepository.findAll();
    }
 
    @RequestMapping("/employee/filter")
    public List<Employee> getFiltered(String firstName,@RequestParam(defaultValue = "0") Double bonusAmount) {
 
        return employeeRepository.getFirstNamesLikeAndBonusBigger(firstName,bonusAmount);
    }
 
}

Исходный код можно найти на github .