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.演示環境
- JDK 1.8.0_201
- Spring Boot 2.2.0.RELEASE
- 構建工具(apache maven 3.6.3)
- 開發工具(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 完成查詢。