1. 程式人生 > 實用技巧 >10.SpringBoot學習(十)——JDBC之 Spring Boot Jpa

10.SpringBoot學習(十)——JDBC之 Spring Boot Jpa

1.簡介

1.1 概述

The Java Persistence API is a standard technology that lets you “map” objects to relational databases. The spring-boot-starter-data-jpa POM provides a quick way to get started. It provides the following key dependencies:

  • Hibernate: One of the most popular JPA implementations.
  • Spring Data JPA: Makes it easy to implement JPA-based repositories.
  • Spring ORMs: Core ORM support from the Spring Framework.

Java Persistence API 是一種標準技術,可讓您將物件“對映”到關係資料庫。 spring-boot-starter-data-jpa POM提供了一種快速入門的方法。它提供以下關鍵依賴性:

  • Hibernate:最流行的JPA實現之一。
  • Spring Data JPA:使基於JPA的儲存庫的實現變得容易。
  • Spring ORMs:Spring 框架對Core ORM的支援。

1.2 特點

  • 基於Spring和JPA構建儲存庫的先進支援
  • 支援 Querydsl 謂詞,從而支援型別安全的JPA查詢
  • 實體類的透明稽核
  • 分頁支援,動態查詢執行,整合自定義資料訪問程式碼的能力
  • 在啟動時驗證 @Query 帶註釋的查詢
  • 支援基於XML的實體對映
  • 通過引入 @EnableJpaRepositories,支援基於 JavaConfig 的儲存庫配置

2.演示環境

  1. JDK 1.8.0_201
  2. Spring Boot 2.2.0.RELEASE
  3. 構建工具(apache maven 3.6.3)
  4. 開發工具(IntelliJ IDEA )

3.演示程式碼

3.1 程式碼說明

演示基於 spring-boot-starter-data-jpa 來操作資料庫的簡單 web mvc 專案。包括以下常用場景:

  • 單表的增、刪、改、查
  • 多表關聯查詢(這裡使用2張表)
  • 複雜條件混合查詢
  • 分頁查詢

3.2 程式碼結構

3.3 maven 依賴

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-lang3</artifactId>
    </dependency>
</dependencies>

3.4 配置檔案

application.properties

spring.datasource.url=jdbc:mysql://172.16.11.125:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# 列印sql
spring.jpa.show-sql=true
# 自動建表
spring.jpa.hibernate.ddl-auto=create
# 方言;innodb儲存引擎
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
# 格式化sql
spring.jpa.properties.hibernate.format_sql=true
# 列印sql中引數
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=trace

spring.data.web.pageable.default-page-size=3
spring.data.web.pageable.page-parameter=pageNum
spring.data.web.pageable.size-parameter=pageSize
spring.data.web.sort.sort-parameter=orderBy

3.5 java程式碼

Order.java

@Entity
@Table(name = "t_order")
public class Order {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    @Column(nullable = false)
    private Long userId;
    @Column(nullable = false, unique = true)
    private String orderCode;
    @Column(nullable = false)
    private BigDecimal totalMoney;
    @Column(nullable = false)
    private String orderDate;

    public Order() {}

    public Order(Long userId, String orderCode, BigDecimal totalMoney, String orderDate) {
        this.userId = userId;
        this.orderCode = orderCode;
        this.totalMoney = totalMoney;
        this.orderDate = orderDate;
    }

   	// get&set&toString
}

User.java

@Entity
@Table(name = "t_user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false, unique = true, length = 32)
    private String name;
    @Column(nullable = false)
    private Integer age;
    private String birthday;
    private String address;
    @Column(nullable = false, length = 16)
    private String phone;

    public User() {}

    public User(String name, Integer age, String birthday, String address, String phone) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
        this.address = address;
        this.phone = phone;
    }

   	// get&set&toString
}

OrderRepository.java

@Repository
public interface OrderRepository extends JpaRepository<Order, Long>, JpaSpecificationExecutor<Order> {

    @Query(value = "select "
        + "o.id as orderId, o.orderCode as orderCode, o.orderDate as orderDate, o.userId as userId, "
        + "u.address as address, u.phone as phone, u.age as age from Order o inner join User u on o.userId = u.id where o.orderCode = ?1")
    OrderInfo selectOrderByCode(String orderCode);
}

UserRepository.java

@Repository
public interface UserRepository extends JpaRepository<User, Long> {

    @Query("select u from User u where u.name = ?1")
    User findUserByName(String name);

    @Query("select u from User u")
    Page<User> findByPage(Pageable pageable);

    @Query("select u from User u where u.phone = :phone")
    List<User> findUserByPhone(@Param("phone") String phone);

    @Modifying
    @Transactional
    @Query("update User set phone = ?1 where name = ?2")
    int updateByName(String phone, String name);

    @Modifying
    @Transactional
    @Query("delete from User where name = :name")
    int deleteByName(@Param("name") String name);
}

OrderService.java

public interface OrderService {

    /**
     * 查詢所有user
     * @return order
     */
    List<Order> selectList();

    /**
     * 根據訂單號關聯查詢
     * @param orderCode 訂單號
     * @return OrderInfo
     */
    OrderInfo selectOrderByCode(String orderCode);

    /**
     * 使用example查詢
     * @param order 查詢引數
     * @return Order
     */
    List<Order> selectByExample(Order order);

    /**
     * 多條件組合查詢
     * @param orderParam 查詢引數
     * @return Order
     */
    Page<Order> selectByCondition(OrderParam orderParam, Pageable pageable);
}

UserService.java

public interface UserService {

    /**
     * 查詢所有資料
     * @return user
     */
    List<User> selectList();

    /**
     * 根據名稱查詢
     * @param name name
     * @return user
     */
    User findUserByName(String name);

    /**
     * 根據電話查詢
     * @param phone 電話
     * @return user
     */
    List<User> findUserByPhone(String phone);

    /**
     * 分頁查詢
     * @param pageable 分頁引數
     * @return user
     */
    Page<User> findByPage(Pageable pageable);

    /**
     * 根據名稱更新電話
     * @param phone 電話
     * @param name 名稱
     * @return 影響行數
     */
    User updateByName(String phone, String name);

    /**
     * 根據名稱刪除
     * @param name 名稱
     * @return 影響行數
     */
    User deleteByName(String name);

    /**
     * 新增
     * @param user user
     * @return user
     */
    User add(User user);
}

UserServiceImpl.java

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserRepository userRepository;

    @Override
    public List<User> selectList() {
        return userRepository.findAll();
    }

    @Override
    public User findUserByName(String name) {
        return userRepository.findUserByName(name);
    }

    @Override
    public List<User> findUserByPhone(String phone) {
        return userRepository.findUserByPhone(phone);
    }

    @Override
    public Page<User> findByPage(Pageable pageable) {
        return userRepository.findByPage(pageable);
    }

    @Override
    public User updateByName(String phone, String name) {
        userRepository.updateByName(phone, name);
        return findUserByName(name);
    }

    @Override
    public User deleteByName(String name) {
        User user = findUserByName(name);
        userRepository.deleteByName(name);
        return user;
    }

    @Override
    public User add(User user) {
        return userRepository.save(user);
    }
}

OrderServiceImpl.java

@Service
public class OrderServiceImpl implements OrderService {

    @Autowired
    private OrderRepository orderRepository;

    @Override
    public List<Order> selectList() {
        return orderRepository.findAll();
    }

    @Override
    public OrderInfo selectOrderByCode(String orderCode) {
        return orderRepository.selectOrderByCode(orderCode);
    }

    @Override
    public List<Order> selectByExample(Order order) {
        // exact:精確比配 contains: 模糊匹配 startsWith:從頭匹配
        // 同 matcher -> matcher.exact();
        ExampleMatcher exampleMatcher = matching().withMatcher("userId", GenericPropertyMatcher::exact)
            .withMatcher("orderCode", GenericPropertyMatcher::contains)
            .withMatcher("orderDate", GenericPropertyMatcher::startsWith);
        Example<Order> example = Example.of(order, exampleMatcher);
        return orderRepository.findAll(example);
    }

    @Override
    public Page<Order> selectByCondition(OrderParam orderParam, Pageable pageable) {
        return orderRepository.findAll((root, query, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            // equal userId
            if (Objects.nonNull(orderParam.getUserId())) {
                predicates.add(cb.equal(root.get("userId"), orderParam.getUserId()));
            }
            // like orderCode
            if (StringUtils.isNotBlank(orderParam.getOrderCode())) {
                predicates.add(cb.like(root.get("orderCode"), "%" + orderParam.getOrderCode() + "%"));
            }
            // between
            if (StringUtils.isNotBlank(orderParam.getOrderStartDate()) && StringUtils.isNotBlank(orderParam.getOrderEndDate())) {
                predicates.add(cb.between(root.get("orderDate"), orderParam.getOrderStartDate(), orderParam.getOrderEndDate()));
            }
            // greater than
            if (Objects.nonNull(orderParam.getTotalMoney())) {
                predicates.add(cb.greaterThan(root.get("totalMoney"), orderParam.getTotalMoney()));
            }
            return query.where(predicates.toArray(new Predicate[0])).getRestriction();
        }, pageable);
    }
}

OrderInfo.java

public interface OrderInfo {

    Long getUserId();
    Long getOrderId();
    Integer getAge();
    String getOrderCode();
    String getAddress();
    String getPhone();
    String getOrderDate();
}

OrderParam.java

public class OrderParam {

    private Long id;
    private Long userId;
    private String orderCode;
    private BigDecimal totalMoney;
    private String orderStartDate;
    private String orderEndDate;
    // get&set
}

OrderController.java

@RestController
@RequestMapping(value = "/order")
public class OrderController {

    @Autowired
    private OrderService orderService;

    @GetMapping(value = "/list")
    public List<Order> list() {
        return orderService.selectList();
    }

    @GetMapping(value = "/queryByCode/{orderCode}")
    public OrderInfo queryByCode(@PathVariable String orderCode) {
        return orderService.selectOrderByCode(orderCode);
    }

    @GetMapping(value = "/queryByExample")
    public List<Order> selectByExample(@RequestBody Order order) {
        return orderService.selectByExample(order);
    }

    @GetMapping(value = "/queryByCondition")
    public Page<Order> queryByCondition(@RequestBody OrderParam orderParam, Pageable pageable) {
        return orderService.selectByCondition(orderParam, pageable);
    }
}

UserController.java

@RestController
@RequestMapping(value = "/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping(value = "/list")
    public List<User> list() {
        return userService.selectList();
    }

    @GetMapping(value = "/findByName/{name}")
    public User findByName(@PathVariable String name) {
        return userService.findUserByName(name);
    }

    @GetMapping(value = "/findByPhone/{phone}")
    public List<User> findByPhone(@PathVariable String phone) {
        return userService.findUserByPhone(phone);
    }

    @GetMapping(value = "/page")
    public Page<User> page(Pageable pageable) {
        return userService.findByPage(pageable);
    }

    @PostMapping(value = "/add")
    public User add(User user) {
        return userService.add(user);
    }

    @PutMapping(value = "/updateByName")
    public User updateByName(@RequestBody User user) {
        return userService.updateByName(user.getPhone(), user.getName());
    }

    @DeleteMapping(value = "/deleteByName/{name}")
    public User deleteByName(@PathVariable String name) {
        return userService.deleteByName(name);
    }
}

InitializeDataCommand.java

@Component
public class InitializeDataCommand implements CommandLineRunner {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private OrderRepository orderRepository;

    @Override
    public void run(String... args) throws Exception {

        User user1 = new User("zhangsan", 20, "2000-01-01", "shenzhen", "13888888888");
        User user2 = new User("lisi", 21, "1999-01-01", "shanghai", "13777777777");
        User user3 = new User("wangwu", 22, "1998-01-01", "beijing", "13666666666");
        User user4 = new User("zhaoliu", 23, "1997-01-01", "guangzhou", "13555555555");
        User user5 = new User("sunqi", 24, "1996-01-01", "wuhan", "13444444444");

        SecureRandom random = SecureRandom.getInstance("SHA1PRNG", "SUN");
        DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
        LocalDateTime now = LocalDateTime.now();
        List<User> users = userRepository.saveAll(Arrays.asList(user1, user2, user3, user4, user5));

        List<Order> orders = users.stream().map(user -> {
            Order order = new Order();
            order.setUserId(user.getId());
            order.setOrderCode("OC202005231205000" + (users.indexOf(user) + 1));
            order.setOrderDate(dateTimeFormatter.format(now.minusDays(random.nextInt(100))));
            order.setTotalMoney(BigDecimal.valueOf(random.nextDouble() * random.nextInt(10000)));
            return order;
        }).collect(Collectors.toList());

        orderRepository.saveAll(orders);
    }
}

3.6 git 地址

spring-boot/spring-boot-06-jdbc/spring-boot-data-jpa

4.效果展示

啟動 SpringBootJpaDemoApplication.main 方法,在 spring-boot-data-jpa.http 訪問下列地址,觀察輸出資訊是否符合預期。

4.1 t_user

查詢使用者列表(所有)

### GET /user/list
GET http://localhost:8080/user/list
Accept: application/json

根據使用者名稱查詢

### GET /user/findByName/{name}
GET http://localhost:8080/user/findByName/lisi
Accept: application/json

根據手機號查詢

### GET /user/findByPhone/{phone}
GET http://localhost:8080/user/findByPhone/13666666666
Accept: application/json

查詢使用者列表(分頁)

### GET /user/page
GET http://localhost:8080/user/page
Accept: application/json
Content-Type: application/json

{
  "pageable":{
     "pageNumber":1,
     "pageSize":3,
     "orderBy":"age desc"
  }
}

更新使用者資訊

### PUT /user/updateByName
PUT http://localhost:8080/user/updateByName
Content-Type: application/json

{
  "name": "zhangsan",
  "phone": "13456789012"
}

刪除使用者

### DELETE /user/deleteByName/{name}
DELETE http://localhost:8080/user/deleteByName/zhangsan
Content-Type: application/json

4.2 t_order

查詢訂單列表(所有)

### GET  /order/list
GET http://localhost:8080/order/list
Accept: application/json

根據訂單編號關聯查詢

### GET /order/queryByCode/{orderCode}
GET http://localhost:8080/order/queryByCode/OC2020052312050002
Accept: application/json

多條件查詢訂單

### GET /order/queryByExample
GET http://localhost:8080/order/queryByExample
Accept: application/json
Content-Type: application/json

{
"userId":2,
"orderCode":"OC202005231",
"orderDate": "2020-05-17"
}

多條件混合查詢

### GET /order/queryByCondition
GET http://localhost:8080/order/queryByCondition
Accept: application/json
Content-Type: application/json

{
  "userId": 2,
  "orderCode": "OC20200523",
  "totalMoney": 20,
  "orderStartDate": "2020-02-10 16:17:12",
  "orderEndDate": "2020-05-30 16:17:12"
}

5.原始碼分析

5.1 @Repository 如何載入的?

SpringBooApplication 應用啟動時,會呼叫 createApplicationContext 方法,這裡指定了預設 web 應用的型別是 AnnotationConfigServletWebServerApplicationContext。在 AnnotationConfigServletWebServerApplicationContext 的建構函式中,呼叫了 AnnotatedBeanDefinitionReader 的構造方法,最終通過 registerAnnotationConfigProcessors 方法將一些和註解掃描相關的 Processor 註冊到 context 中,其中有一個類是 ConfigurationClassPostProcessor,這個比較關鍵。

在呼叫 refreshContext 方法時,最終會呼叫到 AbstractApplicationContext 的 refresh 方法,在這個流程中,invokeBeanFactoryPostProcessors 方法觸發了 ConfigurationClassPostProcessor,將註解進行掃描,從而註冊到 registry 中。

5.2 UserRepository 的動態代理

UserRepository 繼承自 JpaRepository,JpaRepository 有一個 FactoryBean 叫 JpaRepositoryFactoryBean,它實現了InitializingBean 介面,在 afterPropertiesSet 中進行了代理操作。同時它也實現了 FactoryBean 介面,提供一個 getObject 方法來獲取 bean 的例項。

在 factory.getRepository 方法中,有一個 getRepositoryInformation 方法,它的實現如下

private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata,
                                                       RepositoryComposition composition) {

    RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, composition);

    return repositoryInformationCache.computeIfAbsent(cacheKey, key -> {

        // 這裡的 baseClass 為 SimpleJpaRepository
        Class<?> baseClass = repositoryBaseClass.orElse(getRepositoryBaseClass(metadata));

        return new DefaultRepositoryInformation(metadata, baseClass, composition);
    });
}

這裡的 getRepositoryBaseClass 獲取一個 baseClass,實際返回一個 SimpleJpaRepository.class,這個 baseClass 在後面作為被代理物件使用。

在 getTargetRepositoryViaReflection 方法中,根據這個 baseClass,通過反射建立物件

protected final <R> R getTargetRepositoryViaReflection(RepositoryInformation information,
      Object... constructorArguments) {
	// 獲取到 baseClass,即為 SimpleJpaRepository
   Class<?> baseClass = information.getRepositoryBaseClass();
   return getTargetRepositoryViaReflection(baseClass, constructorArguments);
}

	protected final <R> R getTargetRepositoryViaReflection(Class<?> baseClass, Object... constructorArguments) {
		Optional<Constructor<?>> constructor = ReflectionUtils.findConstructor(baseClass, constructorArguments);
		// 通過反射建立物件物件
		return constructor.map(it -> (R) BeanUtils.instantiateClass(it, constructorArguments)).orElseThrow(() -> new IllegalStateException(String.format(
						"No suitable constructor found on %s to match the given arguments: %s. Make sure you implement a constructor taking these",
						baseClass, Arrays.stream(constructorArguments).map(Object::getClass).collect(Collectors.toList()))));
	}

然後將這個物件作為 target 放到 result 中,result 又添加了一些 advisor 和 advice,這些在查詢時被構建成連結器鏈

// 獲取到一個 SimpleJpaRepository 例項
Object target = getTargetRepository(information);

// Create proxy
ProxyFactory result = new ProxyFactory();
// 作為目標物件
result.setTarget(target);
result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);

if (MethodInvocationValidator.supports(repositoryInterface)) {
   result.addAdvice(new MethodInvocationValidator());
}
// 新增 advisor
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);

postProcessors.forEach(processor -> processor.postProcess(result, information));

if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) {
   result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}

// 新增 advice
ProjectionFactory projectionFactory = getProjectionFactory(classLoader, beanFactory);
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory));

composition = composition.append(RepositoryFragment.implemented(target));
result.addAdvice(new ImplementationMethodExecutionInterceptor(composition));
// 獲取代理物件
T repository = (T) result.getProxy(classLoader);

最終生成的代理物件即為如下所示

5.3 Jpa 查詢流程是怎樣的?

這裡以 UserServiceImpl#findUserByName 說一下 jpa 的查詢流程

在 UserServiceImpl 呼叫了 UserRepository,UserRepository 是一個代理物件,它被 JdkDynamicAopProxy 所代理,所以執行 UserRepository 中方法時,會呼叫 JdkDynamicAopProxy 中 invoke 方法。

@Override
@Nullable
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
   Object oldProxy = null;
   boolean setProxyContext = false;

   TargetSource targetSource = this.advised.targetSource;
   Object target = null;

   try {
      if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) {
         // The target does not implement the equals(Object) method itself.
         return equals(args[0]);
      }
      else if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) {
         // The target does not implement the hashCode() method itself.
         return hashCode();
      }
      else if (method.getDeclaringClass() == DecoratingProxy.class) {
         // There is only getDecoratedClass() declared -> dispatch to proxy config.
         return AopProxyUtils.ultimateTargetClass(this.advised);
      }
      else if (!this.advised.opaque && method.getDeclaringClass().isInterface() &&
            method.getDeclaringClass().isAssignableFrom(Advised.class)) {
         // Service invocations on ProxyConfig with the proxy config...
         return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args);
      }

      Object retVal;

      if (this.advised.exposeProxy) {
         // Make invocation available if necessary.
         oldProxy = AopContext.setCurrentProxy(proxy);
         setProxyContext = true;
      }

      // Get as late as possible to minimize the time we "own" the target,
      // in case it comes from a pool.
       // 獲取目標物件
      target = targetSource.getTarget();
      Class<?> targetClass = (target != null ? target.getClass() : null);

      // Get the interception chain for this method.
       // 構建攔截鏈
      List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

      // Check whether we have any advice. If we don't, we can fallback on direct
      // reflective invocation of the target, and avoid creating a MethodInvocation.
      if (chain.isEmpty()) {
         // We can skip creating a MethodInvocation: just invoke the target directly
         // Note that the final invoker must be an InvokerInterceptor so we know it does
         // nothing but a reflective operation on the target, and no hot swapping or fancy proxying.
         Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
         retVal = AopUtils.invokeJoinpointUsingReflection(target, method, argsToUse);
      }
      else {
         // We need to create a method invocation...
         MethodInvocation invocation =
               new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
         // Proceed to the joinpoint through the interceptor chain.
         retVal = invocation.proceed();
      }

      // Massage return value if necessary.
      Class<?> returnType = method.getReturnType();
      if (retVal != null && retVal == target &&
            returnType != Object.class && returnType.isInstance(proxy) &&
            !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) {
         // Special case: it returned "this" and the return type of the method
         // is type-compatible. Note that we can't help if the target sets
         // a reference to itself in another returned object.
         retVal = proxy;
      }
      else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) {
         throw new AopInvocationException(
               "Null return value from advice does not match primitive return type for: " + method);
      }
      return retVal;
   }
   finally {
      if (target != null && !targetSource.isStatic()) {
         // Must have come from TargetSource.
         targetSource.releaseTarget(target);
      }
      if (setProxyContext) {
         // Restore old proxy.
         AopContext.setCurrentProxy(oldProxy);
      }
   }
}

在 JdkDynamicAopProxy 中會通過 getInterceptorsAndDynamicInterceptionAdvice 獲取到一條鏈,實際上它是一個攔截器鏈,它由一下幾個部分組成:

  • ExposeInvocationInterceptor: 將當前的invocation設定到上下文中
  • CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor: 判斷是自定義方法還是jpa中方法,如果是自定義方法直接執行下一個攔截器;否則繫結資源再執行下一個攔截器
  • PersistenceExceptionTranslationInterceptor: 捕獲RuntimeException,出現異常之後攔截器才生效
  • TransactionInterceptor: 給後面要執行的攔截器新增後置事務處理
  • DefaultMethodInvokingMethodInterceptor: 判斷是否 defaultMethod,如果不是走下一個攔截器;否則使用MethodHandle執行
  • RepositoryFactorySupport$QueryExecutorMethodInterceptor: 執行自定義查詢
  • RepositoryFactorySupport$ImplementationMethodExecutionInterceptor:攔截 RepositoryComposition
  • PersistenceExceptionTranslationInterceptor:異常處理攔截器

最終在 QueryExecutorMethodInterceptor 中呼叫 doInvoke 方法執行自定義查詢

@Nullable
private Object doInvoke(MethodInvocation invocation) throws Throwable {

    Method method = invocation.getMethod();
	
    if (hasQueryFor(method)) {
        // 執行查詢
        return queries.get(method).execute(invocation.getArguments());
    }
	// 繼續執行下一個攔截器
    return invocation.proceed();
}

在 execute 中,通過呼叫 AbstractJpaQuery#execute -> AbstractJpaQuery#doExecute -> JpaQueryExecution#execute -> JpaQueryExecution.SingleEntityExecution#doExecute -> AbstractProducedQuery#getSingleResult -> AbstractProducedQuery#list -> AbstractProducedQuery#doList -> org.hibernate.internal.SessionImpl#list,使用 hibernate 完成查詢。

6.參考

  1. 官方 spring-data-jpa
  2. 官方文件-Spring Boot Features/JPA