jpa mysql sha1_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 特点

7c872f8835ec3869ce0a5ded576b98b3.png

基于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 代码结构

1ba9508c7e47ccdbe8bed53c0fcf298d.png

3.3 maven 依赖

org.springframework.boot

spring-boot-starter-data-jpa

org.springframework.boot

spring-boot-starter-web

mysql

mysql-connector-java

org.springframework.boot

spring-boot-starter-test

test

org.apache.commons

commons-lang3

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, JpaSpecificationExecutor {

@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 {

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

User findUserByName(String name);

@Query("select u from User u")

Page findByPage(Pageable pageable);

@Query("select u from User u where u.phone = :phone")

List 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 selectList();

/**

* 根据订单号关联查询

* @param orderCode 订单号

* @return OrderInfo

*/

OrderInfo selectOrderByCode(String orderCode);

/**

* 使用example查询

* @param order 查询参数

* @return Order

*/

List selectByExample(Order order);

/**

* 多条件组合查询

* @param orderParam 查询参数

* @return Order

*/

Page selectByCondition(OrderParam orderParam, Pageable pageable);

}

UserService.java

public interface UserService {

/**

* 查询所有数据

* @return user

*/

List selectList();

/**

* 根据名称查询

* @param name name

* @return user

*/

User findUserByName(String name);

/**

* 根据电话查询

* @param phone 电话

* @return user

*/

List findUserByPhone(String phone);

/**

* 分页查询

* @param pageable 分页参数

* @return user

*/

Page 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 selectList() {

return userRepository.findAll();

}

@Override

public User findUserByName(String name) {

return userRepository.findUserByName(name);

}

@Override

public List findUserByPhone(String phone) {

return userRepository.findUserByPhone(phone);

}

@Override

public Page 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 selectList() {

return orderRepository.findAll();

}

@Override

public OrderInfo selectOrderByCode(String orderCode) {

return orderRepository.selectOrderByCode(orderCode);

}

@Override

public List 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 example = Example.of(order, exampleMatcher);

return orderRepository.findAll(example);

}

@Override

public Page selectByCondition(OrderParam orderParam, Pageable pageable) {

return orderRepository.findAll((root, query, cb) -> {

List 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 list() {

return orderService.selectList();

}

@GetMapping(value = "/queryByCode/{orderCode}")

public OrderInfo queryByCode(@PathVariable String orderCode) {

return orderService.selectOrderByCode(orderCode);

}

@GetMapping(value = "/queryByExample")

public List selectByExample(@RequestBody Order order) {

return orderService.selectByExample(order);

}

@GetMapping(value = "/queryByCondition")

public Page 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 list() {

return userService.selectList();

}

@GetMapping(value = "/findByName/{name}")

public User findByName(@PathVariable String name) {

return userService.findUserByName(name);

}

@GetMapping(value = "/findByPhone/{phone}")

public List findByPhone(@PathVariable String phone) {

return userService.findUserByPhone(phone);

}

@GetMapping(value = "/page")

public Page 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 users = userRepository.saveAll(Arrays.asList(user1, user2, user3, user4, user5));

List 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 地址

4.效果展示

启动 SpringBootJpaDemoApplication.main 方法,在 spring-boot-data-jpa.http 访问下列地址,观察输出信息是否符合预期。

4.1 t_user

查询用户列表(所有)

### GET /user/list

GET http://localhost:8080/user/list

Accept: application/json

6dac05b0bb2ad32ba2181c7e204b458e.png

根据用户名查询

### GET /user/findByName/{name}

GET http://localhost:8080/user/findByName/lisi

Accept: application/json

5efcb06b610128d7512baafa9d8adb09.png

根据手机号查询

### GET /user/findByPhone/{phone}

GET http://localhost:8080/user/findByPhone/13666666666

Accept: application/json

f01401309ceee850455c4c25053deea5.png

查询用户列表(分页)

### GET /user/page

GET http://localhost:8080/user/page

Accept: application/json

Content-Type: application/json

{

"pageable":{

"pageNumber":1,

"pageSize":3,

"orderBy":"age desc"

}

}

875aa4417f9bf07a94c2f8e065a26bf0.png

更新用户信息

### PUT /user/updateByName

PUT http://localhost:8080/user/updateByName

Content-Type: application/json

{

"name": "zhangsan",

"phone": "13456789012"

}

d08c7e5dd3f9526846183d2fbbd13168.png

删除用户

### DELETE /user/deleteByName/{name}

DELETE http://localhost:8080/user/deleteByName/zhangsan

Content-Type: application/json

5a88d10f78adbe4450621d2f191cfe48.png

4.2 t_order

查询订单列表(所有)

### GET /order/list

GET http://localhost:8080/order/list

Accept: application/json

d9686481f1adadabb06caccd21df1012.png

根据订单编号关联查询

### GET /order/queryByCode/{orderCode}

GET http://localhost:8080/order/queryByCode/OC2020052312050002

Accept: application/json

89720019a8f57618b1c3879c8c54244c.png

多条件查询订单

### GET /order/queryByExample

GET http://localhost:8080/order/queryByExample

Accept: application/json

Content-Type: application/json

{

"userId":2,

"orderCode":"OC202005231",

"orderDate": "2020-05-17"

}

e2b25729519ae7216ca28dfa53f8ce21.png

多条件混合查询

### 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"

}

625015853e33304e0d466ec05d8e9417.png

5.源码分析

5.1 @Repository 如何加载的?

SpringBooApplication 应用启动时,会调用 createApplicationContext 方法,这里指定了默认 web 应用的类型是 AnnotationConfigServletWebServerApplicationContext。在 AnnotationConfigServletWebServerApplicationContext 的构造函数中,调用了 AnnotatedBeanDefinitionReader 的构造方法,最终通过 registerAnnotationConfigProcessors 方法将一些和注解扫描相关的 Processor 注册到 context 中,其中有一个类是 ConfigurationClassPostProcessor,这个比较关键。

在调用 refreshContext 方法时,最终会调用到 AbstractApplicationContext 的 refresh 方法,在这个流程中,invokeBeanFactoryPostProcessors 方法触发了 ConfigurationClassPostProcessor,将注解进行扫描,从而注册到 registry 中。

63b52b6def832b81dd98b54facd583f2.png

5.2 UserRepository 的动态代理

902755c6f3ca540419603bfa5ef16d13.png

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 getTargetRepositoryViaReflection(RepositoryInformation information,

Object... constructorArguments) {

// 获取到 baseClass,即为 SimpleJpaRepository

Class> baseClass = information.getRepositoryBaseClass();

return getTargetRepositoryViaReflection(baseClass, constructorArguments);

}

protected final R getTargetRepositoryViaReflection(Class> baseClass, Object... constructorArguments) {

Optional> 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);

5645ceb6baa5a6e6611c10a366290dd6.png

最终生成的代理对象即为如下所示

155cbe1dea9a1ecb4b4fc697ffbf3fbc.png

5.3 Jpa 查询流程是怎样的?

这里以 UserServiceImpl#findUserByName 说一下 jpa 的查询流程

675f0a5fdeb8e8c4228361d8a5630e11.png

在 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 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.参考

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值