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=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.参考
- 官方 spring-data-jpa
- 官方文档-Spring Boot Features/JPA
10.SpringBoot学习(十)——JDBC之 Spring Boot Jpa相关推荐
- springboot 学习十五:Spring Boot 优雅的集成Swagger2、Knife4j
文章目录 导言 一.swagger2介绍 二.springBoot-swagger2实战演练 1. 快速创建项目 2. 引入是swagger2 依赖 3. swagger2 常用注解 4. ==配置 ...
- Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例
<p>这篇文章介绍如何使用 Jpa 和 Thymeleaf 做一个增删改查的示例.</p> 先和大家聊聊我为什么喜欢写这种脚手架的项目,在我学习一门新技术的时候,总是想快速的搭 ...
- 【SpringBoot学习】28、Spring Boot 整合 TKMybatis 通用 Mapper
文章目录 Spring Boot 整合 TKMybatis 自定义父类接口 新增接口 新增之后回显 Id 更新 删除 单查询 查询列表 计数 条件构造器 技术分享区 Spring Boot 整合 TK ...
- (转)Spring Boot (十五): Spring Boot + Jpa + Thymeleaf 增删改查示例
http://www.ityouknow.com/springboot/2017/09/23/spring-boot-jpa-thymeleaf-curd.html 这篇文章介绍如何使用 Jpa 和 ...
- SpringBoot 系列教程(八十五):Spring Boot使用MD5加盐验签Api接口之前后端分离架构设计
加密算法参考: 浅谈常见的七种加密算法及实现 加密算法参考: 加密算法(DES,AES,RSA,MD5,SHA1,Base64)比较和项目应用 目的: 通过对API接口请求报文签名,后端进行验签处理, ...
- Spring Boot干货系列:(十二)Spring Boot使用单元测试 | 嘟嘟独立博客
原文地址 2017-12-28 开启阅读模式 Spring Boot干货系列:(十二)Spring Boot使用单元测试 Spring Boot干货系列 Spring Boot 前言 这次来介绍下Sp ...
- eclipse创建springboot项目_创建一个 Spring Boot 项目,你会几种方法?
我最早是 2016 年底开始写 Spring Boot 相关的博客,当时使用的版本还是 1.4.x ,文章发表在 CSDN 上,阅读量最大的一篇有 42W+,如下图: 2017 年由于种种原因,就没有 ...
- Spring Boot(十四):spring boot整合shiro-登录认证和权限管理
Spring Boot(十四):spring boot整合shiro-登录认证和权限管理 使用Spring Boot集成Apache Shiro.安全应该是互联网公司的一道生命线,几乎任何的公司都会涉 ...
- Spring Boot基础学习笔记18:Spring Boot整合Redis缓存实现
文章目录 零.学习目标 一.Spring Boot支持的缓存组件 二.基于注解的Redis缓存实现 (一)安装与启动Redis (二)创建Spring Boot项目 - RedisCacheDemo0 ...
- 《深入理解 Spring Cloud 与微服务构建》第十六章 Spring Boot Security 详解
<深入理解 Spring Cloud 与微服务构建>第十六章 Spring Boot Security 详解 文章目录 <深入理解 Spring Cloud 与微服务构建>第十 ...
最新文章
- java 业务层业务接口层_Java web五层架构
- $_post 数据上传到那个位置_如何实现图片上传并保存到数据库?
- 线程的应用-继承Thread类创建线程
- 安卓进阶系列-03上弹选择框(PopupDialog)的使用
- C#编程(四十八)----------列表
- java合并单元格同时导出excel
- React开发(200):三种截取字符串的方法
- oracle禁止修改密码,Oracle 用户密码过期后不允许修改密码的示例代码
- akshare写etf动量滚动策略
- linux运行powershell,linux – 是否可以编写一个在bash / shell和PowerShell中运行的脚本?...
- 董明珠解释举报奥克斯初衷:这不仅是企业间的竞争 更是道德的选择
- pandownload限速原因及解决方案
- C++ 中的mutable关键字
- 从shell(终端)中退出python
- 利用oc门或od门实现线与_OC门电路和OD门电路原理
- Android 调用12306接口,聚合数据Android SDK 12306火车票查询订票演示示例 编辑
- 周爱民对提升程序员自身技术能力的建议
- 【步兵 经验篇】one step
- hhkb java_为什么知乎上这么多人推荐 HHKB,却不反复强调说该键盘不适合大多数程序员?...
- Vue packages version mismatch解决方案