Spring +Spirng MVC+Mybatis +SpringBoot
AI ------>>>直接使用阿里的 通义天问
Maven基础
介绍
Maven 介绍
Maven 作用
项目构建 比较简单~
核心功能
依赖管理
<!-- gavp属性-->
<groupId>com.example</groupId>
<artifactId>tials-manage</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 打包方式 jar-->
<packaging>jar</packaging>
<!-- 自定义属性-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
依赖传递
依赖传递 A----->B------>C
导入依赖,会自动导入依赖的依赖 即依赖传递
依赖冲突
解决: 谁短谁 优先—>>>引用的路径长度
继承
不需要写 版本
如果 子工程声明了依赖版本,以 子工程 依赖版本为主
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- gavp属性-->
<groupId>com.example</groupId>
<artifactId>tials-manage</artifactId>
<version>0.0.1-SNAPSHOT</version>
<!-- 打包方式 jar-->
<packaging>jar</packaging>
<!-- 自定义属性-->
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<spring-boot.version>2.6.13</spring-boot.version>
</properties>
<!-- 导入依赖-->
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- 依赖范围-->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
</dependencies>
<!-- 导入插件-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>${spring-boot.version}</version>
<configuration>
<mainClass>com.example.tialsmanage.TialsManageApplication</mainClass>
<skip>true</skip>
</configuration>
<executions>
<execution>
<id>repackage</id>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<!-- 只做 依赖的版本控制,不导入依赖-->
<dependencyManagement>
</dependencyManagement>
</project>
Spring
介绍
IOC
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<!-- 依赖范围-->
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
@ServletComponentScan // 扫描过滤器
public class TialsManageApplication {
public static void main(String[] args) {
ConfigurableApplicationContext ioc = SpringApplication.run (TialsManageApplication.class, args);
Object zs = ioc.getBean ("zhangsan");
}
}
@ComponentScan(basePackages = "com.example.tialsmanage") // 扫描注解,只扫 Spring注解
介绍
@Bean + @Configuration
@Configuration // 配置类
public class PersonConfig {
/**
* 将返回值放入容器
* 默认是单例的
*
* @return
*/
@Scope
@Bean("zhangsan") // 默认方法名就是 bean的id
public User zhangsan() {
return new User ("zhangsan", 18);
}
}
@Component 与衍生 分层注解
/**
* 默认,分层注解 能起作用的前提是: 这些组件 必须在主程序所在的包及其子包结构下。
*
* Spring为我们提供了快速的 MVC分层注解:
* 1. @Controller 控制器
* 2. @Service 服务层
* 3. @Repository 持久层
* 4. @Component 组件
*
* @param args
*/
@Scope + @Lazy
/**
* @Scope 调整组件的作用域:
* 1. @Scope("prototype"): 非单实例:
* 容器启动的时候 不会 创建非单实例组件的对象。
* 什么时候获取,什么时候创建~
* 2. @Scope("singleton"): 单实例: 默认值
* 容器启动的时候 会创建 单实例组件的对象。
* 容器启动完成之前就会创建好
* @Lazy: 懒加载
* 容器启动 完成之前不会创建 懒加载组件的对象
* 什么时候获取,什么时候创建
* 3. @Scope("request"): 同一个请求单实例
* 4. @Scope("session"): 同一次会话单实例
*
* @return
*/
@Configuration
@Scope("singleton")
@Lazy // 单例模式下,可以继续调整为 懒加载
public class WebConfig implements WebMvcConfigurer {
@Autowired
private TokenInterceptor tokenInterceptor;
}
@Conditional 条件注册 + @ConditionalOnMissingBean 没有则注册
MacCondition
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author dc
* @version 1.0
* @date 2025/02/26 23:16
*/
public class MacCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断环境变量中的OS,如果是mac,则返回true
if (context.getEnvironment ().getProperty ("os.name").contains ("Mac")) {
return true;
}
return false;
}
}
WindowsCondition
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author dc
* @version 1.0
* @date 2025/02/26 23:15
*/
public class WindowsCondition implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 判断环境变量中的OS,如果是 windows,则返回true
String property = context.getEnvironment ().getProperty ("os.name");
if (property.contains ("Windows")) {
return true;
}
return false;
}
}
** @Conditional 使用 **
@Conditional(value = WindowsCondition.class)
@Bean
public User bill() {
return new User ("bill", 25);
}
@Conditional(value = MacCondition.class)
@Bean
public User qbs() {
return new User ("qbs", 35);
}
@Autowired + @Resource + @Primary
import com.example.tialsmanage.Interceptor.TokenInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired // 按照类型注入
private TokenInterceptor tokenInterceptor;
@Autowired
private User user;
@Resource
private MacCondition macCondition;
@Bean
public User u2() {
return new User ("李四", 21);
}
@Primary // 按照类型注入时 优先级最高
@Bean
public User u1() {
return new User ("张三", 18);
}
@Bean
public MacCondition macCondition() {
return new MacCondition ();
}
}
@Profile ---- 多环境
@Profile({"dev", "default"})
public User u2() {
return new User ("李四", 21);
}
@Profile("test")
@Bean
public User u1() {
return new User ("张三", 18);
}
spring:
profiles:
active: dev
生命周期
import lombok.AllArgsConstructor;
import lombok.Data;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@Data
@AllArgsConstructor
public class User implements InitializingBean, DisposableBean {
private String name;
private Integer age;
public void initUser() {
System.out.println ("@Bean 初始化User");
}
public void destoryUser() {
System.out.println ("@Bean 销毁User");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println ("afterPropertiesSet");
}
@Override
public void destroy() throws Exception {
System.out.println ("destroy");
}
@PostConstruct // 构造器之后
public void postConstruct() {
System.out.println ("PostConstruct");
}
@PreDestroy
public void preDestroy() {
System.out.println ("PreDestroy");
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class WebConfig {
private MacCondition macCondition;
@Autowired
public void setMacCondition(MacCondition macCondition) {
System.out.println ("自动注入属性"+macCondition); // 在 initMethod之前调用
this.macCondition = macCondition;
}
/**
* initMethod 在属性设置之后-->>set之后
* @return
*/
@Bean(initMethod = "initUser",destroyMethod = "destoryUser")
public User get() {
return new User ("zs", 15);
}
}
import com.example.tialsmanage.config.User;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;
@Component // 拦截所有Bean的后置处理器
public class MyTestBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println ("【postProcessAfterInitialization】: " + beanName);
return bean;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println ("【postProcessBeforeInitialization】: " + beanName);
if (bean instanceof User) { // bean为 User类
User u = (User) bean;
u.setName ("张三测试");
}
return bean;
}
}
单元测试 @SpringBootTest
import com.example.tialsmanage.config.User;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
@Slf4j // lombok 日志
class TialsManageApplicationTests {
@Autowired
User user;
@Test
void contextLoads() {
System.out.println ("Hello World");
}
}
AOP
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.3.12.RELEASE</version>
</dependency>
切面
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Order(1) // 调顺序 ,数字越小--->>>优先级越高
@Component // 交给 spring 管理
@Aspect // 切面类
public class RecordTimeAspect {
/**
* 线程本地变量
* 每个线程一个
*/
private static final ThreadLocal<Long> threadLocal = ThreadLocal.withInitial (() -> 3000L);
@Around("bf()")
public void get() {
long val = threadLocal.get ().longValue ();
System.out.println (val);
}
/**
* 拦截
*/
@Pointcut(" execution(* com.example.tialsmanage.demos.web.UploadController.*(..))") // 抽取切点表达式
public void bf() {
}
@Before("@annotation(Login)") // 拦截所有带Login注解的方法
public void before() {
System.out.println ("before");
}
@After("@annotation(Login)") // 拦截所有带Login注解的方法
public void after() {
System.out.println ("after");
}
}
@After ("@annotation(ReTime)")
public Object recordTime(ProceedingJoinPoint joinPoint) {
long start = System.currentTimeMillis ();
Object proceed = null;
try {
proceed = joinPoint.proceed ();
} catch (Throwable e) {
throw new RuntimeException (e);
}
long end = System.currentTimeMillis ();
System.out.println ("运行时间:" + (end - start));
return proceed;
}
AOP 应用场景
- 日志记录(Logging)
场景:
需要 记录方法 执行时间、参数、返回值或异常信息,但又 不希望日志代码侵入业务逻辑。
实现逻辑:
切面(Aspect):定义日志记录逻辑(如@Around或@AfterThrowing通知)。
切点(Pointcut):匹配需要记录的方法(如 execution(* com.example.service..(…)))。
示例代码(Spring AOP):
@Aspect
@Component
public class LoggingAspect {
private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
@Around("execution(* com.example.service.*.*(..))")
public Object logMethodExecution(ProceedingJoinPoint joinPoint) throws Throwable {
long startTime = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - startTime;
logger.info("Method {} executed in {} ms", joinPoint.getSignature(), duration);
return result;
}
}
优势:
日志代码集中管理,业务代码保持简洁。
修改日志策略时 无需改动业务方法
。
- 事务管理(Transaction Management)
场景:
在数据库操作中自动开启、提交或回滚事务,避免手动重复编写事务代码。
实现逻辑:
声明式事务:通过 @Transactional
注解标记事务边界。
底层实现:Spring AOP代理类拦截注解方法,结合PlatformTransactionManager管理事务。
示例配置:
@Service
public class UserService {
@Transactional
public void createUser(User user) {
// 数据库操作(如插入用户记录)
}
}
优势:
事务控制与业务逻辑解耦。
支持传播行为、隔离级别等高级配置。
- 权限校验(Authentication & Authorization)
场景:
在方法执行前验证用户权限(如角色校验、接口访问控制)。
实现逻辑:
自定义注解:定义权限校验注解(如@RequireRole(“ADMIN”))。
切面拦截:在方法执行前(@Before)校验权限。
示例代码:
@Aspect
@Component
public class SecurityAspect {
@Before("@annotation(RequireRole)") // 拦截标注了RequireRole 注解的方法
public void checkRole(JoinPoint joinPoint) {
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
RequireRole annotation = signature.getMethod().getAnnotation(RequireRole.class);
String requiredRole = annotation.value();
// 从上下文中获取当前用户角色
String currentRole = getCurrentUserRole();
if (!requiredRole.equals(currentRole)) {
throw new AccessDeniedException("权限不足");
}
}
}
优势:
权限逻辑 集中化 ---------->>>避免 每个方法 重复校验
。
通过注解灵活控制权限粒度。
- 性能监控(Performance Monitoring)
场景:
统计方法执行耗时,定位性能瓶颈。
实现逻辑:
使用@Around通知计算执行时间。
将耗时数据推送至监控系统(如Prometheus)。
示例代码:
@Aspect
@Component
public class PerformanceAspect {
@Around("execution(* com.example.service.*.*(..))")
public Object monitorPerformance(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long elapsedTime = System.currentTimeMillis() - start;
Metrics.recordTime(joinPoint.getSignature().getName(), elapsedTime);
return result;
}
}
优势:
无侵入式监控,代码零耦合。
实时发现性能问题。
- 缓存管理(Caching)
场景:
自动缓存方法返回值,减少重复计算或数据库查询。
实现逻辑:
通过@Cacheable注解声明缓存规则。
示例代码:
@Service
public class ProductService {
@Cacheable(value = "products", key = "#id")
public Product getProductById(Long id) {
return productRepository.findById(id); // 仅第一次调用会执行此方法
}
}
底层原理:
Spring AOP拦截方法调用,优先从缓存中读取数据,未命中时执行方法并缓存结果。
- 数据校验(Validation)
场景:
在方法执行前校验参数合法性(如非空检查、格式验证)。
实现逻辑:
结合JSR 303规范(如@Valid、@NotNull)。
自定义切面拦截参数校验。
示例代码:
@Aspect
@Component
public class ValidationAspect {
@Before("execution(* com.example.service.*.*(..)) && args(.., @Valid param)")
public void validateParameters(JoinPoint joinPoint, Object param) {
// 手动触发校验逻辑(如Hibernate Validator)
Set<ConstraintViolation<Object>> violations = validator.validate(param);
if (!violations.isEmpty()) {
throw new ConstraintViolationException(violations);
}
}
}
何时使用AOP?
横切关注点:多个模块需要相同功能(如日志、权限)。
代码复用:避免重复代码(DRY原则)。
解耦需求:非核心逻辑(如监控)与业务逻辑分离。
避免滥用AOP的情况
-
性能敏感场景:AOP代理可能引入额外开销。
-
过度复杂化:简单逻辑直接编码更清晰。
-
调试困难:链式代理可能增加调试复杂度。
循环依赖、三级缓存
源码------>>> 三级缓存
什么是循环依赖?
Spring可以解决 哪些情况
的循环依赖?
Spring 不支持基于构造器注入的循环依赖,但是假如AB循环依赖,如果一个是构造器注入,一个是 setter注入呢?
看看几种情形:
我们来看一下 三级缓存解决循环依赖
的过程:
当 A、B 两个类发生 循环依赖时:
A实例的初始化过程:
创建A实例,实例化的时候把 A对象⼯⼚放⼊三级缓存,表示 A开始实例化了,虽然我这个对象还不完整,但是先曝光出来让大家知道
A注⼊属性时,发现依赖B,此时B还没有被创建出来,所以去实例化B
同样,B注⼊属性时发现依赖A,它就会从缓存里找A对象。依次从⼀级到三级缓存查询A,从三级缓存通过对象⼯⼚拿到A,发现A虽然不太完善,但是存在,把A放⼊⼆级缓存,同时删除三级缓存中的A,此时,B已经实例化并且初始化完成,把B放入⼀级缓存。
接着A继续属性赋值,顺利从⼀级缓存拿到实例化且初始化完成的B对象,A对象创建也完成,删除⼆级缓存中的A,同时把A放⼊⼀级缓存
最后,⼀级缓存中保存着实例化、初始化都完成的A、B对象
所以,我们就知道为什么Spring能解决setter注入的循环依赖了,因为实例化和属性赋值是分开的,所以里面有操作的空间。如果都是构造器注入的化,那么都得在实例化这一步完成注入,所以自然是无法支持了。
为什么要三级缓存?⼆级不⾏吗?
不行,主要是为了⽣成代理对象。如果是没有代理的情况下,使用二级缓存解决循环依赖也是OK的。但是如果存在代理,三级没有问题,二级就不行了。
因为三级缓存中放的是⽣成具体对象的匿名内部类,获取Object的时候,它可以⽣成代理对象,也可以返回普通对象。使⽤三级缓存主要是为了保证不管什么时候使⽤的都是⼀个对象。
假设只有⼆级缓存的情况,往⼆级缓存中放的显示⼀个普通的Bean对象,Bean初始化过程中,通过 BeanPostProcessor 去⽣成代理对象之后,覆盖掉⼆级缓存中的普通Bean对象,那么 可能就导致取到的Bean对象不一致
了。
事务tx
声明式 事务
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.22</version>
</dependency>
实验
/**
* 默认:出现运行时异常会回滚
* 可以指定回滚类型
* 默认只对同一个数据库的事务管理有效
* 分布式事务可以实现跨数据库的事务管理
*
* @param username
* @param bookId
* @param buyNum
*/
@Transactional(rollbackFor = Exception.class,timeout = 15,propagation = Propagation.REQUIRED) // 超时会回滚
public void checkout(String username, Integer bookId, Integer buyNum) {
// 1、查询图书信息
Book book = bookDao.getBookById (bookId);
BigDecimal price = book.getPrice ();
// 2、计算扣减额度
BigDecimal total = new BigDecimal (buyNum).multiply (price);
// 3、扣减余额
accountDao.updateBalanceByUsername (username, total.negate ());
int i = 10 / 0; // 这行代码会导致除以零异常
// 4、扣减库存
bookDao.updateBookStock (bookId, buyNum);
}
细节
- 默认只对同一个数据库的事务管理有效
详见:[Spring](https://blog.csdn.net/qq_30659573/article/details/127581112)
隔离级别、传播行为
双检查锁、IOC容器启动流程
双检查锁
// 私有的 静态实例变量
private static SingletonLazy instance;
// 私有的构造函数,防止外部实例化
private SingletonLazy() {
// 初始化逻辑
}
// 提供一个公共的静态方法,返回类的唯一实例
public static SingletonLazy getInstance() {
// 第一次检查实例是否存在,如果不存在,才进入同步块
if (instance == null) {
synchronized (SingletonLazy.class) {
// 双检查
// 第二次检查实例是否存在,防止多个线程同时进入同步块
if (instance == null) {
instance = new SingletonLazy();
}
}
}
// 返回实例
return instance;
}
IOC启动流程简述
在Java的Spring框架中,IOC容器的启动流程主要包括以下几个步骤:
-
读取 配置文件
- Spring容器启动时会读取配置文件(如XML、注解或Java配置类),解析其中的Bean定义信息。
-
实例化BeanFactory
- 创建
BeanFactory
或其子类(如DefaultListableBeanFactory
)作为IOC容器的核心组件。
- 创建
-
注册Bean定义
- 将解析后的Bean定义信息注册到
BeanDefinitionRegistry
中,为后续的Bean创建做准备。
- 将解析后的Bean定义信息注册到
-
初始化Bean工厂后处理器
- 如果配置中有
BeanFactoryPostProcessor
类型的Bean,则在此阶段调用它们。这些处理器可以在Bean实例化之前修改Bean定义属性。
- 如果配置中有
-
实例化所有单例Bean
- 对于所有非懒加载的单例Bean,Spring容器会提前实例化并初始化它们。这包括:
- 实例化:根据Bean定义创建Bean实例。
- 属性填充:通过依赖注入(DI)设置Bean的属性值。
- 初始化前处理:如果存在
InstantiationAwareBeanPostProcessor
,则在此阶段进行处理。 - 初始化方法调用:调用Bean的初始化方法(如
@PostConstruct
注解的方法或InitializingBean
接口的afterPropertiesSet
方法)。 - 初始化后处理:如果存在
BeanPostProcessor
,则在此阶段调用postProcessAfterInitialization
方法。
- 对于所有非懒加载的单例Bean,Spring容器会提前实例化并初始化它们。这包括:
-
完成容器刷新
- 容器完成所有必要的初始化工作后**,发布容器事件通知所有监听器。**
-
使用容器
- 应用程序可以 通过容器获取Bean实例,并使用这些Bean执行业务逻辑。
-
销毁Bean
- 当容器关闭时,会调用所有实现了
DisposableBean
接口或配置了销毁方法的Bean的销毁方法,以释放资源。
- 当容器关闭时,会调用所有实现了
Spirng MVC
介绍及入门
前后端分离开发: @ResponseBody 与 @RestController
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.5</version>
</dependency>
@RestController // @RestController= @Controller + @ResponseBody
public class hello {
@RequestMapping("/hello")
public String hello()
{
return "hello sd";
}
}
@RequestMapping("/hello/{name}")
// @PathVariable("name")绑定路径中的name到参数 String name
// @GetMapping 、@PostMapping 都可以
public String hello(@PathVariable("name") String name) {
return "hello sd" + name;
}
HTTP 复习
请求头
响应格式
响应状态码
请求处理
1、@RequestParam 接收参数
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@RequestMapping("/hello")
// 将请求参数name 传到参数 String name
public String hello(@RequestParam(value = "name", required = false) String name,
@RequestParam(value = "age", required = false) Integer age) {
return "hello " + name + " " + age;
}
}
2、接受参数 很多时,使用对象 进行封装
此时属性名和参数名 对应即可~
@Data
public class Person {
private String name;
private int age;
private String sex;
private String address;
}
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@RequestMapping("/hello")
public String hello(Person person) {
return "hello " + person;
}
}
3、@RequestBody 将前端 json数据转为 对象 (需要该对象提供 无参构造方法 )
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor // 无参构造,不然无法 将json转为对象
public class Person {
private String name;
private Integer age;
private String sex;
private String address;
}
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@PostMapping("/requestBody")
// @RequestBody 要求对象提供无参构造方法,动态注入
public String hello(@RequestBody Person person) {
return "hello " + person;
}
}
{
"name": "ds",
"age": 25,
"sex": "男",
"address": "北京"
}
4、原生API : HttpServletRequest 、HttpServletResponse、HttpSession
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@GetMapping("/set")
public String set(HttpServletRequest request) {
HttpSession session = request.getSession ();
session.setAttribute ("name", "ds");
return " set ok ";
}
@GetMapping("/get")
public String get(HttpServletRequest request) {
HttpSession session = request.getSession ();
Object name = session.getAttribute ("name");
return String.valueOf (name);
}
}
5、文件上传
/**
* MultipartFile 专门上传文件的
*
* @param person
* @param headerImg
* @param lifeImg
* @return
*/
@GetMapping("/load")
public String load(Person person,
@RequestParam("headerImg") MultipartFile headerImg,
@RequestParam("lifeImg") MultipartFile[] lifeImg) { // 多文件
HttpSession session = request.getSession ();
session.setAttribute ("name", "ds");
return " ok ";
}
# 设置传输文件大小
spring:
servlet:
multipart:
max-request-size: 10MB
max-file-size: 100MB
响应处理
1、返回 json
@ResponseBody 返回 json数据时,需要 对象提供无参构造和 set/get 方法
// 二合一注解
// @ResponseBody 返回json数据
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@GetMapping("/resp")
public String resp(HttpServletRequest req) {
return " Hello World";
}
}
2、文件下载
import org.springframework.core.io.InputStreamResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
@RestController
public class FileDownloadController {
/**
* 文件下载
*
* @return
* @throws IOException
*/
@GetMapping("/download")
public ResponseEntity<InputStreamResource> download() throws IOException {
// 文件路径
String filePath = "C:\\Users\\Administrator\\Desktop\\"+"算法刷题.pdf";
File file = new File (filePath);
// 创建 FileInputStream 对象
FileInputStream inputStream = new FileInputStream (file);
// 解决文件名中文乱码问题
String encode = URLEncoder.encode (file.getName (), "UTF-8");
// 以下代码永远不改
// 文件太大会oom
InputStreamResource resource = new InputStreamResource (inputStream);
// 设置响应头信息
HttpHeaders headers = new HttpHeaders ();
// 内容类型:流
headers.setContentType (MediaType.APPLICATION_OCTET_STREAM);
// 内容大小
headers.setContentLength (inputStream.available ());
// 内容处理方式
headers.set ("Content-Disposition", "attachment; filename=" + encode);
// 返回 ResponseEntity 对象
return ResponseEntity.ok ()
.headers (headers)
.body (resource);
}
}
Restful风格
1、Restful风格 + @PathVariable 入门练习
@PathVariable
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@GetMapping("/get/{id}")
// @PathVariable("id")------->>> 将路径中的id 绑定到 Integer id
public String resp(@PathVariable("id") Integer id) {
return " Hello World "+ id;
}
}
2、三层架构
解耦
3、统一返回对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
/**
* 业务的状态码code,200是成功,剩下都是失败;前后端将来会一起商定 不同的业务状态码前端要 显示不同效果。
* msg: 服务端返回给前端的提示消息
* data: 服务器返回给前端的数据
*
* 示例:
* {
* "code": 300,
* "msg": "余额不足",
* "data": null
* }
*
* 前端 统一处理:
* 1. 前端发送请求,接受服务器数据
* 2. 判断状态码,成功就显示数据,失败就显示提示消息(或者执行其他操作)。
*/
private Integer code;
private String msg;
private Object data;
public static Result ok(Object data) {
return new Result (200, "success", data);
}
}
{
"code": 200,
"msg": "success",
"data": {
"name": "ds",
"age": 15,
"sex": "男",
"address": "北京"
}
}
4、跨域处理
浏览器要 遵循同源策略
@RestController // @RestController= @Controller + @ResponseBody
@RequestMapping("/api/v1")
public class Hello {
@GetMapping("/get/{id}")
public String resp(@PathVariable("id") Integer id) {
return " Hello World "+ id;
}
}
解决跨域
过滤器 Filter 实现:
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
chain.doFilter(req, res);
}
@Override
public void init(FilterConfig filterConfig) {}
@Override
public void destroy() {}
}
注解方式: @CrossOrigin // 允许跨域,注意:加在类上与 加在方法上的区别
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
/**
* CORS policy: 同源策略
* 解决跨域: 在Controller上加注解 @CrossOrigin
*/
@CrossOrigin // 允许跨域,注意:加在类上与 加在方法上的区别
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@GetMapping("/get/{id}")
public String resp(@PathVariable("id") Integer id) {
return " Hello World " + id;
}
}
拦截器
拦截 Controller
拦截器:
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
@Order(1) // 多个拦截器时,拦截器执行顺序,值越小,优先级越高
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
// 在请求处理之前进行拦截逻辑
response.getWriter ().write ("No Permission!");
return false; // 返回true表示 继续处理请求,返回false则中断请求处理
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
// 在请求处理之后 进行拦截逻辑
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
// 在请求完成之后 进行拦截逻辑
}
}
配置类:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private MyInterceptor myInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor (myInterceptor)
.addPathPatterns ("/**") // 拦截所有请求
.excludePathPatterns ("/login", "/public/**"); // 排除特定路径
}
}
异常处理
全局 异常处理
1、统一返回对象
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
private Integer code; // 状态码
private String msg; // 状态码对应的信息
private Object data; // 返回的数据
public static Result ok(Object data) {
return new Result (200, "success", data);
}
public static Result error(Integer code, String msg) {
return new Result (code, msg, null);
}
}
2、 定义 异常枚举类
/**
* 枚举类,用于表示订单模块中的异常状态。
*/
public enum BizExceptionEnum {
ORDER_CLOSED (10001, "订单已关闭"),
ORDER_NOT_EXIST (10002, "订单不存在"),
ORDER_TIMEOUT (10003, "订单超时"),
PRODUCT_STOCK_NOT_ENOUGH (20003, "库存不足"),
PRODUCT_HAS_SOLD (20002, "商品已售完"),
PRODUCT_HAS_CLOSED (20001, "商品已下架");
private final int code;
private final String msg;
BizExceptionEnum(int code, String msg) {
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
3、定义 异常类 ,继承 RuntimeException
import lombok.Data;
@Data
// 继承 RuntimeException
public class BizException extends RuntimeException {
private Integer code; // 业务异常码
private String msg; // 业务异常信息
public BizException(Integer code, String message) {
super (message);
this.code = code;
this.msg = message;
}
public BizException(BizExceptionEnum exceptionEnum) {
super (exceptionEnum.getMsg ());
this.code = exceptionEnum.getCode ();
this.msg = exceptionEnum.getMsg ();
}
}
4、全局异常 处理器
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@RestControllerAdvice // 告诉 Spring,这个类是一个全局异常处理类
public class GlobalExceptionHandler {
/**
* 默认 从上往下找
* 可以建立 异常处理文档
*/
@ExceptionHandler(ArithmeticException.class)
public Result ArithmeticException(ArithmeticException ex) {
return Result.error (201, "数学错误");
}
/**
* 业务异常
*
* @param ex
* @return
*/
@ExceptionHandler(BizException.class)
public Result handleBizException(BizException ex) {
return Result.error (ex.getCode (), ex.getMsg ());
}
@ExceptionHandler(Exception.class)
public Result handleAllExceptions(Exception ex) {
return Result.error (500, "未知异常");
}
}
5、 测试
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@GetMapping("/math")
public Result resp() {
int i = 1 / 0; // 数学异常
return Result.ok ("OK");
}
@GetMapping("/biz")
public Result biz() {
int stock = -1;
if (stock < 0) {
// 业务异常,中断业务逻辑
throw new BizException (BizExceptionEnum.ORDER_CLOSED);
}
return Result.ok ("OK");
}
}
数据校验
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<version>2.7.5</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
统一返回类
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
public class Result {
private Integer code; // 状态码
private String msg; // 状态码对应的信息
private Object data; // 返回的数据
public static Result ok(Object data) {
return new Result (200, "success", data);
}
public static Result error(Integer code, String msg) {
return error (code, msg, null);
}
public static Result error(Integer code, String msg, Object data) {
return new Result (code, msg, data);
}
}
自定义校验注解
// 校验注解,需要绑定校验器
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
validatedBy = {GenderValidator.class} // 指定校验器去完成校验
)
public @interface Gender {
String message() default "性别只能为:男/女";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
自定义校验器
import org.example.Gender;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
public class GenderValidator implements ConstraintValidator<Gender, String> { // 校验类型:String
/**
* @param value 前端提交来的,准备让我们进行校验的属性值
* @param context 校验上下文
* @return 返回校验结果
*/
@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
return "男".equals (value) || "女".equals (value);
}
}
用户类User
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.Email;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.Size;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Size(min = 2, max = 4, message = "用户名长度必须在2-4之间")
private String name;
@Max(value = 100, message = "最大不超过100岁")
@Min(0)
private Integer age;
private String sex;
@Email(message = "邮箱格式不正确")
private String email;
private String address;
@Size(min = 11, max = 11, message = "手机号不正确")
private String phone;
@Gender // 自定义校验注解
private String gender;
}
全局异常处理
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
import java.util.stream.Collectors;
@RestControllerAdvice // 告诉 Spring,这个类是一个全局异常处理类
public class GlobalExceptionHandler {
/**
* 默认 从上往下找
* 可以建立 异常处理文档
*/
@ExceptionHandler(ArithmeticException.class)
public Result ArithmeticException(ArithmeticException ex) {
return Result.error (201, "数学错误");
}
// 处理参数校验
@ExceptionHandler(MethodArgumentNotValidException.class)
public Result handleMethodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult bindingResult = ex.getBindingResult ();
// 使用 HashMap封装 字段校验错误信息
Map<String, Object> map = bindingResult.getFieldErrors ()
.stream ()
.collect (Collectors.toMap (e -> e.getField (), e -> e.getDefaultMessage ()));
return Result.error (500, "校验失败", map);
}
@ExceptionHandler(Exception.class)
public Result handleAllExceptions(Exception ex) {
return Result.error (500, "未知异常");
}
}
Controller 控制器
import org.example.Result;
import org.example.User;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
@CrossOrigin
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@PostMapping("/test")
public Result resp(@RequestBody @Valid User user) {
return Result.ok (user);
}
}
测试
最佳实践
1、JavaBean 分层
2、 VO的最佳实践
@PostMapping("/employee")
public Result add(@RequestBody @Valid EmployeeAddVo vo) {
// 把vo转为do:
Employee employee = new Employee();
// 属性对拷,Spirng 提供
BeanUtils.copyProperties(vo, employee);
// 保存员工信息
employeeService.saveEmp(employee);
return Result.ok();
}
3、接口文档
Swagger 接口文档
源码流程图【面试】
Mybatis
入门
1、安装插件
2、导入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.4.3</version>
</dependency>
<!-- web 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- MySQL JDBC 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>compile</scope>
</dependency>
3、yaml
# 配置MySQL
spring:
profiles:
active: test
datasource:
url: jdbc:mysql://localhost:3306/db?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
# 日志
logging:
level:
# 设置mapper的日志级别为debug
org.example.mapper: debug
# 添加MyBatis配置
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL语句输出到控制台
mapper-locations: classpath:mapper/*.xml
4 、 pojo
@Data
public class Emp {
private Integer id;
private String name;
private Integer age;
private String sex;
private String email;
private String address;
private String phone;
private double salary;
}
5、Mapper 接口
@Mapper // Spring 会自动扫描这个接口,并创建一个实现类,并注入到 Spring 容器中
public interface EmpMapper {
// 可以直接使用 #{对象属性名} 获取参数值
void insertEmp(Emp emp);
// 更新员工信息
void updateEmp(@Param("e") Emp emp);
void deleteEmpByID(@Param("id") Integer id);
Emp selectEmpById(Integer id);
// 实际业务需要分页,不然容易OOM
List<Emp> selectAllEmps();
List<Emp> selectComplexEmp();
@MapKey ("id")
Map<Integer,Emp> getAllMap();
}
6、mapper .xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.example.mapper.EmpMapper">
<!-- #{name} 预编译SQL: 可以防止SQL注入-->
<!--
useGeneratedKeys:自动生成主键
keyProperty:指定主键的属性名,将生成的主键值 赋给id属性
-->
<insert id="insertEmp" useGeneratedKeys="true" keyProperty="id">
insert into emp (name, age, sex, email, address, phone, salary)
values (#{name}, #{age}, #{sex}, #{email}, #{address}, #{phone}, #{salary})
</insert>
<!-- 使用e-->
<update id="updateEmp">
update emp
set name = #{e.name},
age = #{e.age},
sex = #{e.sex},
email = #{e.email},
address = #{e.address},
phone = #{e.phone},
salary = #{e.salary}
WHERE id = #{id}e.
</update>
<delete id="deleteEmpByID">
delete from emp where id = #{id}
</delete>
<select id="selectEmpById" resultType="org.example.pojo.Emp">
select * from emp where id = #{id}
</select>
<!-- 返回集合,要写集合中的元素类型-->
<select id="selectAllEmps" resultType="org.example.pojo.Emp">
select * from emp
</select>
<!-- 返回 map集合,resultType 写map中value的类型-->
<select id="getAllMap" resultType="java.util.Map">
select * from emp
</select>
<!-- 定义 ResultMap -->
<resultMap id="empResultMap" type="org.example.pojo.Emp">
<!--
property:Emp的属性名
column:数据库的列名
-->
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="sex" column="sex"/>
<result property="email" column="email"/>
<result property="address" column="address"/>
<result property="phone" column="phone"/>
<result property="salary" column="salary"/>
</resultMap>
<!-- 复杂查询,使用 ResultMap 进行封装 -->
<select id="selectComplexEmp" resultMap="empResultMap">
select id, name, age, sex, email, address, phone, salary
from emp
where age > 25 and salary > 5000
</select>
</mapper>
数据库连接池
Mybatis 数据库连接池
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.15</version>
</dependency>
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/user
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource # 数据库 连接池
druid:
initial-size: 5 # 初始化大小
max-active: 20 # 最大连接数
min-idle: 5 # 最小连接数
关联查询
association 一对一
@Data
public class Order {
private Long id;
private String address;
private BigDecimal amount;
private Long customerId;
private Customer customer;
}
@Data
public class Customer {
private Long id;
private String customerName;
private String phone;
}
@Mapper
public interface OrderMapper {
Order getOrderById(@Param("id") Integer id);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="org.example.mapper.OrderMapper">
<select id="getOrderById" resultMap="OrderRM">
select o.*,
c.id c_id,
c.customer_name,
c.phone
from t_order o
left join t_customer c on o.customer_id = c.id
where o.id = #{id}
</select>
<!-- 自定义结果集-->
<resultMap id="OrderRM" type="org.example.pojo.Order">
<id column="id" property="id"/>
<result column="address" property="address"/>
<result column="amount" property="amount"/>
<result column="customer_id" property="customerId"/>
<association property="customer" javaType="org.example.pojo.Customer">
<id column="c_id" property="id"/>
<result column="customer_name" property="customerName"/>
<result column="phone" property="phone"/>
</association>
</resultMap>
</mapper>
collection 一对多
@Data
public class Customer {
private Long id;
private String customerName;
private String phone;
// 订单表
List<Order> orders;
}
@Data
public class Order {
private Long id;
private String address;
private BigDecimal amount;
private Long customerId;
private Customer customer;
}
<!-- CutomerRM -->
<resultMap id="CutomerRM" type="org.example.pojo.Customer">
<id column="c_id" property="id"/>
<result column="customer_name" property="customerName"/>
<result column="phone" property="phone"/>
<!-- collection: 说明一对N的封装规则
ofType: 集合中的元素类型
-->
<collection property="orders" ofType="org.example.pojo.Order">
<id column="id" property="id"/>
<result column="address" property="address"/>
<result column="amount" property="amount"/>
<result column="c_id" property="customerId"/>
</collection>
</resultMap>
<!-- select Customer -->
<select id="getCustomerByIdWithOrders" resultMap="CutomerRM">
select c.id c_id,
c.customer_name,
c.phone,
o.*
from t_customer c
left join t_order o on c.id = o.customer_id
where c.id = #{id}
</select>
分步 查询
1、association 分步
@Data
public class Order {
private Long id;
private String address;
private BigDecimal amount;
private Long customerId;
private Customer customer;
}
<!-- OrderCustomerStepRM -->
<resultMap id="OrderCustomerStepRM" type="org.example.pojo.Order">
<id column="id" property="id"></id>
<result column="address" property="address"></result>
<result column="amount" property="amount"></result>
<result column="customer_id" property="customerId"></result>
<!-- customer属性关联一个对象,启动下一次查询,查询这个客户 -->
<association property="customer"
select="getCustomerById"
column="customer_id">
</association>
</resultMap>
<select id="getCustomerById">
select * from t_customer where id=#{id}
</select>
2、 collection 分步
<!-- CutomerRM -->
<resultMap id="CutomerRM" type="org.example.pojo.Customer">
<id column="c_id" property="id"/>
<result column="customer_name" property="customerName"/>
<result column="phone" property="phone"/>
<!-- collection: 说明一对N的封装规则
ofType: 集合中的元素类型
-->
<collection property="orders"
ofType="org.example.pojo.Order"
select="getOrderById"
column="id">
</collection>
</resultMap>
<!-- select Customer -->
<select id="getCustomerByIdWithOrders" resultMap="CutomerRM">
select c.id c_id,
c.customer_name,
c.phone,
o.*
from t_customer c
left join t_order o on c.id = o.customer_id
where c.id = #{id}
</select>
3、开启延迟加载
mybatis-plus:
configuration:
map-underscore-to-camel-case: true
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # SQL语句输出到控制台
lazy-loading-enabled: true # 懒加载
mapper-locations: classpath:mapper/*.xml
分页查询
1、分页查询
Mapper
@Mapper
public interface EmpMapper {
// 统计员工数量
@Select("select count(*) from emp left join dept on emp.dept_id = dept.id")
public Long count();
/**
* 分页 查询
* 查询后 将d.name 取别名为 deptName
* 因为 Emp类没有deptName属性,故在Emp类中添加deptName属性--->>进行封装
*
* @return
*/
@Select("select e.*,d.name deptName from emp e left join dept d on e.dept_id = d.id" +
"order by e.update_time desc limit #{start},#{size}")
public List<Emp> list(@Param("start") Integer start, @Param("size") Integer size);
}
Controller
@Slf4j
@RestController
@RequestMapping("/Dept") // 访问的公共前缀
public class DeptController {
@GetMapping("/add")
public Result add() {
return Result.ok();
}
}
2、PageHelper
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.5</version>
</dependency>
@Mapper
public interface EmpMapper {
/**
* PageHelper 实现分页,只需要指定从哪张表中查询,返回哪些字段
*
* @return
*/
@Select("select e.*,d.name deptName from emp e left join dept d on e.dept_id = d.id" +
"order by e.update_time desc")
public List<Emp> list();
}
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DeptService {
@Autowired
private EmpMapper empMapper;
public PageInfo<Emp> selectList(Integer start, Integer size) {
// 设置分页参数
// 动态代理 实现分页
PageHelper.startPage (start, size);
// 执行查询
// Page extends ArrayList<>
Page<Emp> page = (Page<Emp>) empMapper.list ();
// 封装分页结果
return new PageInfo<Emp> (page.getTotal (), page.getResult ());
}
}
Controller
@Slf4j
@RestController
@RequestMapping("/Dept") // 访问的公共前缀
public class DeptController {
@Autowired
private DeptService deptService;
@GetMapping("/add")
public Result add(Integer start, Integer size) {
PageInfo<Emp> pageInfo = deptService.selectList (start, size);
long total = pageInfo.getTotal ();
List<Emp> empList = pageInfo.getList ();
return Result.ok (empList);
}
}
3、条件分页查询----->>> 优化版
请求参数太多的 优化,使用
对象 封装参数
动态SQL
1、 if 与 where 标签
@Mapper
public interface EmpMapper {
/**
* 查询
* @param empQueryParam
* @return
*/
public List<Emp> list(EmpQueryParam empQueryParam );
}
@Autowired
private DeptService deptService;
@GetMapping("/emps")
public Result add(EmpQueryParam empQueryParam) {
PageInfo<Emp> pageInfo = deptService.selectList (empQueryParam);
long total = pageInfo.getTotal ();
List<Emp> empList = pageInfo.getList ();
return Result.ok (empList);
}
Mapper 文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.tialsmanage.demos.web.EmpMapper">
<select id="list" resultType="com.example.tialsmanage.demos.web.Demp">
select emp.*,dept.name deptName from emp left join dept on dept.id=emp.dept_id
-- #{name} 不能用在 ''中
<where>
<if test="name !=null and name !='' ">
and emp.name like concat('%',#{name},'%')
</if>
<if test="gender !=null ">
and emp.gender=#{gender}
</if>
<if test="begin !=null and end !=null ">
and emp.entry_date between #{begin} and #{end}
</if>
</where>
</select>
</mapper>
2、set 标签
3、foreach 标签
void insertBatch(List<Emp> empList);
<insert id="insertBatch">
insert into emp(name, gender, entry_date, dept_id) values
<foreach collection="empList" item="emp" separator=",">
(#{emp.name}, #{emp.gender}, #{emp.entry_date}, #{emp.deptId})
</foreach>
</insert>
考虑使用 Mybatis-Plus
问题
插入几个数据之后 出现问题,无法回滚
此时考虑 事务
使用事务控制----> @Transactional
–>指定回滚类型(可见 事务管理内容)
缓存机制
1、事务级别 缓存 ( 一级缓存)
默认:事务期间 会开启缓存
2、二级缓存
** 注意:pojo 需要实现 序列化接口,不然会报错 ~~~**
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="org.example.mapper.OrderMapper">
<!-- 所有的查询都会共享到二级缓存-->
<cache/> <!-- 开启 二级缓存-->
<select id="getCustomerById">
select *
from t_customer
where id = #{id}
</select>
</mapper>
@Data
public class Order implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String address;
private BigDecimal amount;
private Long customerId;
private Customer customer;
}
SpringBoot
介绍
简化 部署
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
使用Maven命令
mvn clean package
来 清理项目并进行打包。执行该命令后,会在项目的 target目录下生成一个可执行的Jar文件。
场景启动器
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
自动配置【面试、核心】
1、初步理解
2、自动导入 配置类
根据条件 @Conditional 注解 进行 导入
再 配置组件,将配置文件与属性类 绑定
基础
1、依赖
<dependencies>
<!-- web 依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.5</version>
</dependency>
<!-- MySQL JDBC 驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.8.9</version>
</dependency>
</dependencies>
2、 yaml 配置
# 配置mysql
spring:
datasource:
url: jdbc:mysql://localhost:3306/dw_test?useSSL=false&serverTimezone=UTC
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
3、启动类
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Main {
public static void main(String[] args) {
SpringApplication
.run (Main.class, args);
}
}
4、Controller 控制类
import org.example.Result;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
@CrossOrigin
@RestController // @RestController= @Controller + @ResponseBody
public class Hello {
@PostMapping("/test")
public Result resp(@RequestBody String name) {
return Result.ok (name);
}
}
日志
1、lombok 依赖
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
</dependency>
2、日志级别
# 日志级别
logging:
level:
root: debug
3、日志 输出到文件
# 日志级别
logging:
file:
# 指定输出的文件路径
path: D://aaa.log
name: boot.log
level:
root: debug
4、测试
@CrossOrigin
@RestController // @RestController= @Controller + @ResponseBody
@Slf4j
public class Hello {
@PostMapping("/test")
public Result resp(@RequestBody String name) {
log.info("接收到请求,请求参数为:{}", name);
return Result.ok (name);
}
}
总结:
多环境
@Configuration
public class WebConfig {
@Bean
@Profile("dev")
public Result r1() {
return Result.ok ("User dev");
}
@Bean
@Profile("test")
public Result r2() {
return Result.ok ("User test");
}
}
# 指定环境
spring:
profiles:
active: test
@CrossOrigin
@RestController // @RestController= @Controller + @ResponseBody
@Slf4j
public class Hello {
@Autowired
private Result result;
@GetMapping("/test")
public Result resp() {
log.info("接收到请求,请求参数为:{}", result);
return Result.ok ("OK");
}
}
单元测试
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<version>2.7.5</version>
</dependency>
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest // Spring提供的测试环境
@Slf4j
public class HTest {
@Test
public void test() {
System.out.println ("OK 666");
}
}