[豪の学习笔记] Spring框架学习碎碎念#1

跟学视频:黑马JavaWeb课程

三层架构

image-20250615164919374

​ controller:控制层,接收前端发送的请求,对请求进行处理并响应数据

​ service:业务逻辑层,处理具体的业务逻辑

​ dao:数据访问层(Data Access Object),也叫持久层,负责数据访问操作,包括数据的增删改查

分层解耦

​ 控制反转:Inversion Of Control,简称IOC。对象的创建控制权由程序自身转移到外部(容器),这种思想称为控制反转

​ 依赖注入:Dependency Injection,简称DI。容器为应用程序提供运行时所依赖的资源,称之为依赖注入

​ Bean对象:IOC容器中创建、管理的对象,称之为bean

@Component //将当前类交给IOC容器管理,成为IOC容器中的bean
@Autowired //运行时,IOC容器会提供该类型的bean对象并赋值该变量,即依赖注入

​ Service层及Dao层的实现类交给IOC容器管理,,为Controller及Service注入运行时依赖的对象

Bean的声明

​ 要把某个对象交给IOC容器管理,需要在对应的类上加上如下注解之一:

注解 说明 位置
@Component 声明bean的基础注解 不属于以下三类时使用此注解
@Controller @Component的衍生注解 标注在控制器类上
@Service @Component的衍生注解 标注在业务类上
@Repository @Component的衍生注解 标注在数据访问类上
(由于与MyBatis整合,使用少)

​ 声明bean的时候,可以通过value属性指定bean的名字,如果没有指定则默认为类名首字母小写

​ 使用以上四个注解都可以声明bean,但是在springboot集成web开发中,声明控制器bean只能用@Controller

Bean组件扫描

​ 前面声明bean的四大注解想要生效,还需要被组件扫描注解,@ComponentScan扫描

​ @ComponentScan注解虽然没有显式配置,但实际上已经包含在了启动类声明注解@SpringBootApplication中,默认扫描的范围是启动类所在包及其子包

Bean注入

​ @Autowired注解,默认是按照类型进行自动装配,如果存在多个相同类型的bean将会报错,通过以下三种方案解决:

​ @Primary:

@Primary
@Service
public class EmpServiceA implements EmpService{}

​ @Qualifier:

@RestController
public class EmpController{
	@Autowired
	@Qualifier("empServiceA")
	private EmpService empService;
}

​ @Resource:

@RestController
public class EmpController{
    @Resource(name="empServiceB")
    private EmpService empService;
}

​ 其中@Resource与@Autowired的区别是:@Autowired是Spring框架提供的注解,默认按照类型注入;而@Resource是JDK提供的注解,默认按照名称注入

MyBatis

配置数据库连接信息 - 四要素

# 驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据库连接的URL
spring.datasource.url=jdbc:mysql://localhost:3306/mybatis
# 连接数据库的用户名
spring.datasource.username=root
# 连接数据库的密码
spring.datasource.password=XXXXXXX

数据库连接池

​ 数据库连接池是个容器,负责分配、管理数据库连接

​ 它允许应用程序重复使用一个现有的数据库连接。而不是再重新建立一个

​ 释放空闲时间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

优点:资源重用、提升系统响应速度、避免数据库连接遗漏

标准接口:DataSource

​ 官方(sun)提供的数据库连接池接口,由第三方组织实现

​ 功能:获取连接 Connection getConnection() throws SQLException;

常见产品:Hikari(springboot默认)、Druid、DBCP、C3P0

Druid德鲁伊:Druid连接池是阿里巴巴开源的数据库连接池项目,功能强大,性能优秀,是Java语言最好的数据库连接池之一

lombok

​ Lombok是一个实用的Java类库,能通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发,提高效率

注解 作用
@Getter/@Setter 为所有的属性提供get/set方法
@ToString 会给类自动生成易阅读的toString方法
@EqualsAndHashCode 根据类所拥有的非静态字段,自动重写equals方法和hashCode方法
@Data 提供了更综合的生成代码功能(@Getter + @Setter + @ToString + @EqualsAndHashCode)
@NoArgsConstructor 为实体类生成无参的构造器方法
@AllArgsConstructor 为实体类生成除了static修饰的字段之外,带有各参数的构造器方法

​ Lombok会在编译时自动生成对应的Java代码,使用Lombok时还需要安装一个lombok的插件(IDEA自带)

参数占位符

#{...}

​ 执行SQL时,会将#{...}替换为?,生成预编译SQL,会自动设置参数值

​ 使用时机:参数传递

${...}

​ 拼接SQL,直接将参数拼接在SQL语句中,存在SQL注入问题

​ 使用时机:如果对表名、列表进行动态设置时使用

基础操作CRUD

根据ID删除员工
@Delete("DELETE FROM emp WHERE id = #{id}")
public void delete(Integer id);
新增员工
@Options(keyProperty = "id", useGeneratedKeys = true) //会自动将生成的主键值赋值给emp对象的id属性
@Insert("INSERT INTO emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) VALUES (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime});")
public void insert(Emp emp);
更新员工信息
@Update("UPDATE emp SET username = #{username}, name = #{name}, gender = #{gender}, image = #{image}, job = #{job}, entrydate = #{enrtydate}, dept_id = #{deptId}, create_time = #{createTime}, update_time = #{updateTime} WHERE id = #{id}")
public void update(Emp emp);
根据ID查询员工
@Select("SELECT * FROM emp WHERE id = #{id}")
public Emp getById(Integer id);
根据条件查询员工
@Select("SELECT * FROM emp WHERE name LIKE CONCAT('%', #{name}, '%') AND gender = #{gender} AND entrydate BETWEEN #{begin} AND #{end} ORDER BY update_time DESC")
public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);

数据封装

​ 实体类属性名和数据库表查询返回的字段名一致,mubatis会自动封装

​ 如果实体类属性名和数据库表查询返回的字段名不一致,则不能自动封装

①给字段起别名,让别名与实体类属性一致

@Select("SELECT id, username, password, name, gender, image, job, entrydate, dept_id deptId, create_time createTime, update_time updateTime FROM emp WHERE id = #{id}")
public Emp getById(Integer id);

②通过@Results、@Result注解手动映射封装

@Results({
            @Result(column = "dept_id", property = "deptId"),
            @Result(column = "create_time", property = "createTime"),
            @Result(column = "update_time", property = "updateTime")
    })
@Select("SELECT * FROM emp WHERE id = #{id}")
public Emp getById(Integer id);

③在properties中开启mybatis的驼峰命名自动映射的开关 a_column ---> aColumn

mybatis.configuration.map-underscore-to-camel-case=true

动态SQL

​ 随着用户的输入或外部条件的变化而变化的SQL语句

XML映射文件

​ XML映射文件的名称与Mapper接口名称一致,并且将XML映射文件和Mapper接口放置在相同包下,即同包同名

​ XML映射文件的namespace属性为Mapper接口全限定名一致

​ XML映射文件中SQL语句的id与Mapper接口中的方法名一致,并保持返回类型

@Mapper
public interface EmpMapper{
    public List<Emp> list(String name, Short gender, LocalDate begin, LocalDate end);
}
<mapper namespace="com.magicshushu.mapper.EmpMapper">
    <select id="list" resultType="com.magicshushu.pojo.Emp">
        SELECT * FROM emp WHERE name LIKE concat('%', #{name}, '%') AND gender = #{gender} AND entrydate BETWEEN #{begin} AND #{end} ORDER BY update_time DESC
    </select>
</mapper>

​ 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,还会让本就复杂的 SQL 语句更加混乱不堪。 因此,如果需要做一些很复杂的操作,最好用 XML 来映射语句

​ 选择何种方式来配置映射,以及是否应该要统一映射语句定义的形式,完全取决于个人和团队。 不要拘泥于一种方式,可以在基于注解和 XML 的语句映射方式间自由移植和切换

if标签

用于判断条件是否成立,使用test属性进行条件判断,如果条件为true则拼接SQL

where元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的AND或OR

用在UPDATE语句中,动态地在行首插入SET关键字,并会删除额外的逗号

<where>
	<if test="name != null">
		name LIKE concat('%', #{name}, '%')
	</if>
	<if test="gender != null">
		AND gender = #{gender}
	</if>
	<if test="begin != null and end != null">
		AND entrydate BETWEEN #{begin} AND #{end}
	</if>
</where>
<update id="update2">
UPDATE emp
	<set>
		<if test="username != null">
			username = #{username},
		</if>
		<if test="name != null">
			name = #{name},
		</if>
		<if test="gender != null">
			gender = #{gender},
		</if>
		<if test="image != null">
			image = #{image},
		</if>
		<if test="job != null">
			job = #{job},
		</if>
		<if test="entrydate != null">
			entrydate = #{enrtydate},
		</if>
		<if test="deptId != null">
			dept_id = #{deptId},
		</if>
		<if test="updateTime != null">
			update_time = #{updateTime}
		</if>
	</set>
	WHERE id = #{id}
</update>
foreach标签
<!--根据ID批量删除员工-->
<!--
	collection: 遍历的集合
	item: 遍历出来的元素
	separator: 分隔符
	open: 遍历开始前拼接的SQL片段
	close: 遍历结束后拼接的SQL片段
-->
<delete id="deleteByIds">
	DELETE FROM emp WHERE id IN
	<foreach collection="ids" item="id" separator="," open="(" close=")">
		#{id}
	</foreach>
</delete>
sql + include标签

定义可重用的SQL片段

通过属性refid指定包含的sql片段

<sql id="commonSelect">
	SELECT id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
	FROM emp
</sql>

<select id="list" resultType="com.magicshushu.pojo.Emp">
	<include refid="commonSelect"/>
	<where>
		<if test="name != null">
			name LIKE concat('%', #{name}, '%')
		</if>
		<if test="gender != null">
			AND gender = #{gender}
		</if>
		<if test="begin != null and end != null">
			AND entrydate BETWEEN #{begin} AND #{end}
		</if>
	</where>
	ORDER BY update_time DESC
</select>

开发规范-Restful

​ REST(REpresentational State Transfer),表述性状态转换,它是一种软件架构风格

传统URL风格URL:

http://localhost:8080/user/getById?id=1     GET:查询id为1的用户
http://localhost:8080/user/saveUser         POST:新增用户
http://localhost:8080/user/updateUser       POST:修改用户
http://localhost:8080/user/deleteUser?id=1  GET:删除id为1的用户

​ 原始的传统URL,定义较复杂,而且将资源的访问行为对外暴露

基于REST风格URL:

http://localhost:8080/users/1  GET:查询id为1的用户
http://localhost:8080/users    POST:新增用户
http://localhost:8080/users    PUT:修改用户
http://localhost:8080/users/1  DELETE:删除id为1的用户

​ REST是风格,是约定方式,不是规定,可以打破

​ 描述模块的功能通常使用复数,也就是加s的格式来描述,表示此类资源而非单个资源,如users、emps、books

统一响应结果Result

​ 前后端工程在进行交互时,使用统一响应结果 Result

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Integer code;//响应码,1 代表成功; 0 代表失败
    private String msg;  //响应信息 描述字符串
    private Object data; //返回的数据

    //增删改 成功响应
    public static Result success(){
        return new Result(1,"success",null);
    }
    //查询 成功响应
    public static Result success(Object data){
        return new Result(1,"success",data);
    }
    //失败响应
    public static Result error(String msg){
        return new Result(0,msg,null);
    }
}

项目开发流程

​ 查看页面原型明确需求 ---> 阅读接口文档 ---> 思路分析 ---> 接口开发 ---> 接口测试 ---> 前后端联调

文件上传

String getOriginalFilename(); //获取原始文件名
void transferTo(File dest); //将接收的文件转存到磁盘文件中
long getSize(); //获取文件的大小 单位是字节
byte[] getBytes(); //获取文件内容的字节数组
InputStream getInputStream(); //获取接收到的文件内容的输入流
@Slf4j
@RestController
public class UploadController {

    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile image) throws Exception {
        log.info("文件上传: {}, {}, {}", username, age, image);

        //获取原始文件名
        String originalFilename = image.getOriginalFilename();

        //构造唯一文件名 通用唯一识别码uuid
        int index = originalFilename.lastIndexOf(".");
        String extname = originalFilename.substring(index);
        String newFileName = UUID.randomUUID().toString() + extname;
        log.info("新的文件名: {}", newFileName);

        //将文件存储在服务器的磁盘目录中
        image.transferTo(new File("C:\\Develop\\" + newFileName));

        return Result.success();
    }
}

​ 在SpringBoot中,文件上传默认单个文件允许最大大小为1M,如果需要上传大文件,可以进行如下配置:

#配置单个文件最大上传大小
spring.servlet.multipart.max-file-size=10MB

#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB

yml配置文件

配置格式

​ SpringBoot提供了多种属性配置方式

application.properties

server.port=8080
server.address=127.0.0.1

application.yml / application.yaml

server:
	port: 8080
	address: 127.0.0.1

基本语法

​ 大小写敏感

​ 数值前面必须有空格,作为分隔符

​ 使用缩进表示层级关系,缩进时不允许使用Tab键,只能用空格(IDEA中会自动将Tab转换为空格)

​ 缩进的空格数目不重要,只要相同层级的元素左端对齐即可

​ #表示注释,从这个字符一直到行尾都会被解释器忽略

yml数据格式

对象/Map集合

user:
  name: zhangsan
  age: 18
  password: 123456

数组/List/Set集合

hobby:
  - java
  - game
  - sport

配置优先级

  • 命令行参数(--xxx=xxx)
  • Java系统属性(-Dxxx=xxx)
  • application.properties
  • application.yml
  • application.yaml

@ConfigurationProperties与@Value

相同点

​ 都是用来注入外部配置的属性

不同点

​ @Value注解只能一个一个的进行外部属性的注入

​ @ConfigurationProperties可以批量地将外部的属性配置注入到bean对象的属性中

登录校验

image-20250625154834538

登陆标记:用户登陆成功后每一次请求中都可以获取到该标记

统一拦截:过滤器Filter、拦截器Interceptor

会话技术

会话

​ 用户打开浏览器访问web服务器的资源,会话建立,直到有一方断开连接,会话结束

​ 在一次会话中可以包含多次请求和响应

会话跟踪

​ 一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便在同一次会话的多次请求间共享数据

会话跟踪方案

客户端会话跟踪技术:Cookie

image-20250625162202627

​ 优点:HTTP协议中支持的技术

​ 缺点:移动端APP无法使用Cookie、不安全,用户可以自己禁用Cookie、Cookie不能跨域(跨域区分三个维度:协议、IP/域名、端口)

服务端会话跟踪技术:Session

image-20250627140922809

​ 优点:存储在服务器,较为安全

​ 缺点:服务器集群环境下无法直接使用Session,Cookie的缺点

令牌技术

image-20250627141628211

​ 优点:支持PC端、移动端,解决集群环境下的认证问题,减轻服务器端存储压力

​ 缺点:需要自己实现

JWT令牌

JWT

​ JWT,JSON Web Token,定义了一种简洁的、自包含的格式,用于在通信双方以json数据格式安全的传输信息

​ 由于数字签名的存在,这些信息是可靠的

组成

image-20250627142845096

​ 第一部分:Header(头),记录令牌类型、签名算法等

​ 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等

​ 第三部分:Signature(签名),防止Token被篡改、确保安全性,将Header、Payload融合并加入指定密钥,通过指定签名算法计算而来

登录认证场景

​ ①登录成功后,生成令牌

​ ②后续每个请求都要携带JWT令牌,系统在每次请求处理之前先校验令牌,通过后再处理

生成与解析
	/**
     * 生成JWT
     */
    @Test
    public void testGenJwt(){
        Map<String, Object> claims = new HashMap<>();
        claims.put("id", 1);
        claims.put("name", "tommy");

        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "magicshushu")  //签名算法
                .setClaims(claims) //自定义内容(载荷)
                .setExpiration(new Date(System.currentTimeMillis() + 3600*1000)) //设置有效期为1h
                .compact();

        System.out.println(jwt);
    }


    /**
     * 解析JWT
     */
    @Test
    public void testParseJwt(){
        Claims claims = Jwts.parser()
                .setSigningKey("magicshushu")
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tbXkiLCJpZCI6MSwiZXhwIjoxNzUxMDEzNzI1fQ.g9OeA7ok0rOcZhuRsV67N5T2h4a7gjNgwRtyTIkwgUY")
                .getBody();
        System.out.println(claims);
    }

​ JWT校验时使用的签名密钥,必须和生成JWT令牌时使用的密钥是配套的

​ 如果JWT令牌解析校验时报错,则说明JWT令牌被篡改或失效了,令牌非法

过滤器Filter

​ Filter过滤器是JavaWeb三大组件(Servlet、Filter、Listener)之一

​ 过滤器可以把对资源的请求拦截下来,从而实现一些特殊的功能

​ 过滤器一般完成一些通用的操作,如登录校验、统一编码处理、敏感字符处理等

image-20250628171309251

拦截路径 urlPatterns值 含义
拦截具体路径 /login 只有访问/login路径时才会被拦截
目录拦截 /emps/* 访问/emps下的所有资源都会被拦截
拦截所有 /* 访问所有资源都会被拦截

​ 一个Web应用中可以配置多个过滤器,这多个过滤器就形成了一个过滤器链

​ 注解配置的Filter,优先级是按照过滤器类名(字符串)的自然排序

image-20250628172453008

拦截器Interceptor

​ 拦截器Interceptor是一种动态拦截方法调用的机制,类似于过滤器。Spring框架中提供的,用来动态拦截控制器方法的执行

​ 拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码

拦截路径

​ 拦截器可以根据需求,配置不同的拦截路径:

@Override
public void addInterceptors(InterceptorRegistry registry){
      registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
}
拦截路径 含义 举例
/* 一级路径 能匹配/depts,/emps,/login
不能匹配/depts/1
/** 任意级路径 能匹配/depts,/depts/1,/depts/1/2
/depts/* /depts下的一级路径 能匹配/depts/1
不能匹配/depts,/depts/1/2
/depts/** /depts下的任意级路径 能匹配/depts,/depts/1,/depts/1/2
不能匹配/emps/1

拦截器执行流程

image-20250707202600804

Filter与Interceptor

​ 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口

​ 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源

异常处理

全局异常处理器

image-20250707204107756

@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class) //捕获所有异常
    public Result ex(Exception e) {
        e.printStackTrace();
        return Result.error(e.getMessage());
    }
}

@RestControllerAdvice = @ControllerAdvice + @ResponseBody

posted @ 2025-07-07 20:51  SchwarzShu  阅读(11)  评论(0)    收藏  举报