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=tracespring.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 {@Autowiredprivate UserRepository userRepository;@Overridepublic List<User> selectList() {return userRepository.findAll();}@Overridepublic User findUserByName(String name) {return userRepository.findUserByName(name);}@Overridepublic List<User> findUserByPhone(String phone) {return userRepository.findUserByPhone(phone);}@Overridepublic Page<User> findByPage(Pageable pageable) {return userRepository.findByPage(pageable);}@Overridepublic User updateByName(String phone, String name) {userRepository.updateByName(phone, name);return findUserByName(name);}@Overridepublic User deleteByName(String name) {User user = findUserByName(name);userRepository.deleteByName(name);return user;}@Overridepublic User add(User user) {return userRepository.save(user);}
}

OrderServiceImpl.java

@Service
public class OrderServiceImpl implements OrderService {@Autowiredprivate OrderRepository orderRepository;@Overridepublic List<Order> selectList() {return orderRepository.findAll();}@Overridepublic OrderInfo selectOrderByCode(String orderCode) {return orderRepository.selectOrderByCode(orderCode);}@Overridepublic 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);}@Overridepublic Page<Order> selectByCondition(OrderParam orderParam, Pageable pageable) {return orderRepository.findAll((root, query, cb) -> {List<Predicate> predicates = new ArrayList<>();// equal userIdif (Objects.nonNull(orderParam.getUserId())) {predicates.add(cb.equal(root.get("userId"), orderParam.getUserId()));}// like orderCodeif (StringUtils.isNotBlank(orderParam.getOrderCode())) {predicates.add(cb.like(root.get("orderCode"), "%" + orderParam.getOrderCode() + "%"));}// betweenif (StringUtils.isNotBlank(orderParam.getOrderStartDate()) && StringUtils.isNotBlank(orderParam.getOrderEndDate())) {predicates.add(cb.between(root.get("orderDate"), orderParam.getOrderStartDate(), orderParam.getOrderEndDate()));}// greater thanif (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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate UserRepository userRepository;@Autowiredprivate OrderRepository orderRepository;@Overridepublic 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 为 SimpleJpaRepositoryClass<?> 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,即为 SimpleJpaRepositoryClass<?> 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

10.SpringBoot学习(十)——JDBC之 Spring Boot Jpa相关推荐

  1. springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j

    文章目录 导言 一.swagger2介绍 二.springBoot-swagger2实战演练 1. 快速创建项目 2. 引入是swagger2 依赖 3. swagger2 常用注解 4. ==配置 ...

  2. Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例

    <p>这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例.</p> 先和大家聊聊我为什么喜欢写这种脚手架的项目,在我学习一门新技术的时候,总是想快速的搭 ...

  3. 【SpringBoot学习】28、Spring Boot 整合 TKMybatis 通用 Mapper

    文章目录 Spring Boot 整合 TKMybatis 自定义父类接口 新增接口 新增之后回显 Id 更新 删除 单查询 查询列表 计数 条件构造器 技术分享区 Spring Boot 整合 TK ...

  4. (转)Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例

    http://www.ityouknow.com/springboot/2017/09/23/spring-boot-jpa-thymeleaf-curd.html 这篇文章介绍如何使用 Jpa 和 ...

  5. SpringBoot 系列教程(八十五):Spring Boot使用MD5加盐验签Api接口之前后端分离架构设计

    加密算法参考: 浅谈常见的七种加密算法及实现 加密算法参考: 加密算法(DES,AES,RSA,MD5,SHA1,Base64)比较和项目应用 目的: 通过对API接口请求报文签名,后端进行验签处理, ...

  6. Spring Boot干货系列:(十二)Spring Boot使用单元测试 | 嘟嘟独立博客

    原文地址 2017-12-28 开启阅读模式 Spring Boot干货系列:(十二)Spring Boot使用单元测试 Spring Boot干货系列 Spring Boot 前言 这次来介绍下Sp ...

  7. eclipse创建springboot项目_创建一个 Spring Boot 项目,你会几种方法?

    我最早是 2016 年底开始写 Spring Boot 相关的博客,当时使用的版本还是 1.4.x ,文章发表在 CSDN 上,阅读量最大的一篇有 42W+,如下图: 2017 年由于种种原因,就没有 ...

  8. Spring Boot(十四):spring boot整合shiro-登录认证和权限管理

    Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...

  9. Spring Boot基础学习笔记18:Spring Boot整合Redis缓存实现

    文章目录 零.学习目标 一.Spring Boot支持的缓存组件 二.基于注解的Redis缓存实现 (一)安装与启动Redis (二)创建Spring Boot项目 - RedisCacheDemo0 ...

  10. 《深入理解 Spring Cloud 与微服务构建》第十六章 Spring Boot Security 详解

    <深入理解 Spring Cloud 与微服务构建>第十六章 Spring Boot Security 详解 文章目录 <深入理解 Spring Cloud 与微服务构建>第十 ...

最新文章

  1. java 业务层业务接口层_Java web五层架构
  2. $_post 数据上传到那个位置_如何实现图片上传并保存到数据库?
  3. 线程的应用-继承Thread类创建线程
  4. 安卓进阶系列-03上弹选择框(PopupDialog)的使用
  5. C#编程(四十八)----------列表
  6. java合并单元格同时导出excel
  7. React开发(200):三种截取字符串的方法
  8. oracle禁止修改密码,Oracle 用户密码过期后不允许修改密码的示例代码
  9. akshare写etf动量滚动策略
  10. linux运行powershell,linux – 是否可以编写一个在bash / shell和PowerShell中运行的脚本?...
  11. 董明珠解释举报奥克斯初衷:这不仅是企业间的竞争 更是道德的选择
  12. pandownload限速原因及解决方案
  13. C++ 中的mutable关键字
  14. 从shell(终端)中退出python
  15. 利用oc门或od门实现线与_OC门电路和OD门电路原理
  16. Android 调用12306接口,聚合数据Android SDK 12306火车票查询订票演示示例 编辑
  17. 周爱民对提升程序员自身技术能力的建议
  18. 【步兵 经验篇】one step
  19. hhkb java_为什么知乎上这么多人推荐 HHKB,却不反复强调说该键盘不适合大多数程序员?...
  20. Vue packages version mismatch解决方案

热门文章

  1. 短视频搬运神器,二次剪辑神器,涨粉热门必备软件,黑科技----效果杠杠的
  2. 跑跑卡丁车rush服务器维护,跑跑卡丁车RUSH
  3. 空城计课件软件测试,空城计课件参考
  4. 搜索引擎工作原理解析
  5. 分清视频质量中的各种电影视频格式标
  6. P3369 【模板】普通平衡树 Treap树堆学习笔记
  7. 自同步如果服务器删掉文件,linux服务器上ftp删掉的文件能找回
  8. Oracle10g或Oracle11g完全卸载正确步骤(亲身体验-详细图文教程)
  9. python中的ix是啥_python ix
  10. 一键去win7快捷方式小箭头