Spring
概述
什么是Spring框架
核心思想是控制反转(把对象创建和依赖管理的工作交给框架,不用手动new对象,降低代码耦合度)和面向切面编程(将日志、事务等重复的代码抽出来,单独管理)
Spring中的设计模式有哪些
工厂模式:创建和管理bean对象
单例模式:bean默认单例,避免重复创建浪费资源
代理模式:aop实现方式,不修改源代码基础上在目标方法前后加日志、事务等逻辑
IOC
什么是IOC
控制反转(Inversion of Control),DI依赖注入是主要实现方式。传统开发对象的创建、对象之间的依赖需要手动设置,ioc将控制权交给容器,有容器负责对象的创建、装配和对象之间的依赖关系管理。可以降低代码耦合度。
<!-- applicationContext.xml -->
<bean id="userService" class="com.example.UserService">
<property name="userDao" ref="userDao"/>
</bean>
<bean id="userDao" class="com.example.UserDao"/>
// 不再这样写
// UserService userService = new UserService(new UserDao());
// 而是通过容器获取
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = context.getBean("userService", UserService.class);@Component
public class UserDao {
// ...
}
@Service
public class UserService {
@Autowired
private UserDao userDao;
}
@SpringBootApplication
public class MyApp {
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(MyApp.class, args);
UserService userService = context.getBean(UserService.class);
// 使用 userService,其内部的 userDao 已由 Spring 自动注入
}
}什么是依赖注入,有哪些实现方式?
依赖注入:在对象创建时,由容器自动将依赖对象注入到需要依赖的对象中。构造器注入、setter注入、@Autowired注入
// UserDao.java
public class UserDao {
public void save() {
System.out.println("User saved via DAO");
}
}
// UserService.java
public class UserService {
private UserDao userDao;
// 1. 构造器(用于构造器注入)
public UserService(UserDao userDao) {
this.userDao = userDao;
}
// 2. Setter 方法(用于 Setter 注入)
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
}<bean id="userDao" class="com.example.UserDao" />
<bean id="userService" class="com.example.UserService">
<!-- 调用 setUserDao(userDao) 方法 -->
<property name="userDao" ref="userDao" />
</bean><!-- 定义 userDao Bean -->
<bean id="userDao" class="com.example.UserDao" />
<!-- 通过 constructor-arg 注入依赖 -->
<bean id="userService" class="com.example.UserService">
<constructor-arg ref="userDao" />
</bean>
@Service
public class UserService {
@Autowired
private UserDao userDao; // 直接注入字段
public void register() {
userDao.save();
}
}IOC和DI的关系
ioc是一种思想,解决对象依赖管理问题;di是ioc思想的一种实现方式,包括三种注入方式
Bean
什么是Bean
IOC容器管理的对象称为Bean
将一个类声明为Bean的注解有哪些?
@Component-标注任意一个类
@Repository-用于数据库相关操作
@Service-服务层
@Controller-控制层
@Bean-手动定义Bean,主要引入第三方类时使用
@Component/@Service/@Controller/@Repository
class UtilityTool {
public String process(String input) {
return "Processed: " + input;
}
}
//@Bean使用举例
// 假设这是一个你无法修改源码的第三方类
class ThirdPartyLogger {
public void log(String message) {
System.out.println("[ThirdPartyLogger] " + message);
}
}
// 配置类,用于定义 @Bean
@org.springframework.context.annotation.Configuration
class AppConfig {
@Bean
public ThirdPartyLogger thirdPartyLogger() {
return new ThirdPartyLogger(); // 手动创建并交给 Spring 容器管理
}
}
// 可选:在 Service 中使用这个第三方 Bean
@Service
class LoggingService {
@Autowired
private ThirdPartyLogger logger;
public void doSomething() {
logger.log("Business operation started.");
}
}注入Bean的注解有哪些
@Autowired-默认按照类型寻找Bean,如果一个类型有多个Bean,要搭配@Qualifier注解指定具体名称
@Resource-默认按照名称寻找Bean,找不到再按类型
@Inject-按照类型寻找,多个类型Bean,要用@Named制定名称
@Autowired
@Qualifier("emailService")
private MessageService emailService;
@Resource
private MessageService wechatService; // 自动找名为 "wechatService" 的 Bean
// 也可以显式指定 name
@Resource(name = "defaultService")
private MessageService fallbackService;AOP
什么是AOP
Aspect-Oriented Programming,面向切面编程。通过代理的方式,在调用想要的对象方法时,进行拦截处理,执行切入的逻辑,然后调用真正的方法实现。实际开发中常用在记录日志、检查登录权限等场景。
@Aspect
@Component
public class ComprehensiveLoggingAspect {
// 1. @Pointcut:定义可复用的切入点(匹配 com.example.service 包下所有 public 方法)
@Pointcut("execution(public * com.example.service..*(..))")
public void serviceMethods() {}
// 2. @Before:前置通知 —— 方法执行前
@Before("serviceMethods()")
public void logBefore(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("[Before] Calling method: " + methodName +
" with arguments: " + Arrays.toString(args));
}
// 3. @After:后置通知 —— 方法执行后(无论是否异常)
@After("serviceMethods()")
public void logAfter(JoinPoint joinPoint) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[After] Finished method: " + methodName);
}
// 4. @AfterReturning:返回通知 —— 方法成功返回后
@AfterReturning(pointcut = "serviceMethods()", returning = "result")
public void logAfterReturning(JoinPoint joinPoint, Object result) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AfterReturning] Method " + methodName +
" returned: " + result);
}
// 5. @AfterThrowing:异常通知 —— 方法抛出异常时
@AfterThrowing(pointcut = "serviceMethods()", throwing = "exception")
public void logAfterThrowing(JoinPoint joinPoint, Throwable exception) {
String methodName = joinPoint.getSignature().getName();
System.out.println("[AfterThrowing] Exception in " + methodName +
": " + exception.getClass().getSimpleName() + " - " + exception.getMessage());
}
// 6. @Around:环绕通知 —— 完全控制方法调用(可用于性能监控、重试等)
@Around("serviceMethods()")
public Object logExecutionTime(ProceedingJoinPoint pjp) throws Throwable {
long start = System.currentTimeMillis();
// 获取方法名
MethodSignature signature = (MethodSignature) pjp.getSignature();
String methodName = signature.getMethod().getName();
try {
System.out.println("[Around] Starting " + methodName);
Object result = pjp.proceed(); // 执行目标方法
long duration = System.currentTimeMillis() - start;
System.out.println("[Around] " + methodName + " completed in " + duration + " ms");
return result;
} catch (Throwable t) {
long duration = System.currentTimeMillis() - start;
System.out.println("[Around] " + methodName + " failed after " + duration + " ms");
throw t; // 重新抛出异常,保证行为一致
}
}
}Spring AOP和AspectJ AOP区别
spring aop集成了aspectj,spring-aop轻量简单,aspectj功能全面但复杂
AspectJ定义的通知类型
Before-目标方法执行之前
After-不管目标方法有没有异常,执行完都会触发
After Returning-目标方法正常返回结果才会执行
After Throwing-目标方法抛出异常才会执行
Around-包裹目标方法,控制方法的执行过程
注解
重要的Spring注解
@Controller、@Service、@Repository、@RestController(@Controller+@ResponseBody,返回值放入响应体)、@Component、@Bean
@RequestMapping、@ResponseBody、@PathVariable、@RequestParam、@RequestBody、@GetMapping、@PostMapping、@PutMapping、@DeleteMapping
@Autowired、@Qualifier、@Resource
@Aspect、@Before、@After、@Around、@Pointcut
// ==================== Controller 层(Web 层)====================
@RestController
@RequestMapping("/api/users")
public class UserController {
@Autowired
private UserService userService;
// GET /api/users/1
@GetMapping("/{id}")
public UserDto getUser(@PathVariable Long id) {
return userService.getUserById(id);
}
// POST /api/users
@PostMapping
public UserDto createUser(@RequestBody CreateUserRequest request) {
return userService.createUser(request.getName(), request.getAge());
}
// 异常处理(Controller 层常用)
@ExceptionHandler(UserNotFoundException.class)
@ResponseStatus(HttpStatus.NOT_FOUND)
public String handleUserNotFound(UserNotFoundException ex) {
return ex.getMessage();
}
}@Component和@Bean有什么区别
@Component作用于类,作为Bean装配到容器中;而@Bean作用于方法,方法的返回对象会被Spring管理,一般使用第三方库时使用
@RequestMapping和@GetMapping区别
@RequestMapping:注解在类或方法上,可进行get、post、put、delete等请求方法
@GetMapping:注解在方法上,只能使用Get请求
@Restcontroller和@Controller区别
@Restcontroller:在@Controller基础上,增加了@ReponseBody注解,适合在前后端分离的架构下,提供Restful API,返回例如json数据格式
@RequestParam和@PathVariable区别
@RequestParam:从请求的参数部分拿值,/user?name=张三,拿到张三
@PathVariable:从请求的url拿值,/user/123,拿到123
事务
如何启动事务
@Transactional,在方法上注解
Spring MVC
MVC是什么
Model、View、Controller 模型、视图、控制器
模型(包括service和dao):处理数据和业务逻辑、数据存储
视图:用户看到的界面
控制器:接收用户输入,调用模型处理,再把结果给视图显示
Mybatis
什么是Mybatis
ORM(对象关系映射)框架,内部封装了JDBC,开发时只需要关注SQL本身
import java.sql.*;
public class JdbcWithExplicitDriver {
public static void main(String[] args) {
// 数据库信息
String driverClassName = "com.mysql.cj.jdbc.Driver"; // MySQL 8+ 驱动类
String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";
String username = "root";
String password = "123456";
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
// ✅ 步骤 1: 显式加载驱动(不省略!)
Class.forName(driverClassName);
System.out.println("✅ 驱动加载成功: " + driverClassName);
// ✅ 步骤 2: 获取连接
conn = DriverManager.getConnection(url, username, password);
System.out.println("✅ 数据库连接成功");
// ✅ 步骤 3: 创建 PreparedStatement
String sql = "SELECT id, name FROM users WHERE age > ?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, 18); // 设置参数
// ✅ 步骤 4: 执行查询并处理结果
rs = stmt.executeQuery();
System.out.println("✅ 查询结果:");
while (rs.next()) {
long id = rs.getLong("id");
String name = rs.getString("name");
System.out.println("ID: " + id + ", Name: " + name);
}
} catch (ClassNotFoundException e) {
System.err.println("❌ 驱动类未找到: " + e.getMessage());
e.printStackTrace();
} catch (SQLException e) {
System.err.println("❌ 数据库操作失败: " + e.getMessage());
e.printStackTrace();
} finally {
// ✅ 步骤 5: 关闭资源(按顺序:ResultSet → Statement → Connection)
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
System.out.println("✅ 所有资源已关闭");
} catch (SQLException e) {
System.err.println("❌ 关闭资源时出错: " + e.getMessage());
}
}
}
}Mybatis编程步骤
public interface UserMapper {
User selectById(Long id);
List<User> selectAll();
void insert(User user);
void update(User user);
void delete(Long 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">
<!-- namespace 必须是 Mapper 接口的全限定名 -->
<mapper namespace="com.example.mybatis.mapper.UserMapper">
<!-- 结果映射(可省略,但推荐写) -->
<resultMap id="UserResultMap" type="com.example.mybatis.entity.User">
<id property="id" column="id"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
</resultMap>
<!-- 查询单个 -->
<select id="selectById" parameterType="long" resultMap="UserResultMap">
SELECT id, name, age FROM users WHERE id = #{id}
</select>
<!-- 查询所有 -->
<select id="selectAll" resultMap="UserResultMap">
SELECT id, name, age FROM users
</select>
<!-- 插入(支持自增主键回填) -->
<insert id="insert" parameterType="com.example.mybatis.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, age) VALUES (#{name}, #{age})
</insert>
<!-- 更新 -->
<update id="update" parameterType="com.example.mybatis.entity.User">
UPDATE users SET name = #{name}, age = #{age} WHERE id = #{id}
</update>
<!-- 删除 -->
<delete id="delete" parameterType="long">
DELETE FROM users WHERE id = #{id}
</delete>
</mapper><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 1. 类型别名(简化 XML 中的类型书写) -->
<typeAliases>
<package name="com.example.mybatis.entity"/>
</typeAliases>
<!-- 2. 环境配置(数据库连接) -->
<environments default="development">
<environment id="development">
<!-- 事务管理器:JDBC(手动 commit/rollback) -->
<transactionManager type="JDBC"/>
<!-- 数据源:使用 MyBatis 内置连接池 -->
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<!-- 3. 注册 Mapper XML 文件 -->
<mappers>
<!-- 方式1:注册单个 XML -->
<!-- <mapper resource="mapper/UserMapper.xml"/> -->
<!-- 方式2:自动扫描整个包(推荐) -->
<package name="com.example.mybatis.mapper"/>
</mappers>
</configuration>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">
<!-- namespace 必须是 Mapper 接口的全限定名 -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- ==================== 1. resultMap:结果映射(处理列名与属性名不一致、关联对象)==================== -->
<resultMap id="UserResultMap" type="com.example.entity.User">
<id property="id" column="user_id"/> <!-- 主键映射 -->
<result property="name" column="user_name"/> <!-- 普通字段映射 -->
<result property="age" column="user_age"/>
<!-- 一对一关联:用户所属部门 -->
<association property="department" javaType="com.example.entity.Department">
<id property="id" column="dept_id"/>
<result property="name" column="dept_name"/>
</association>
</resultMap>
<!-- ==================== 2. sql:SQL 片段复用 ==================== -->
<sql id="userColumns">
u.id AS user_id, u.name AS user_name, u.age AS user_age,
d.id AS dept_id, d.name AS dept_name
</sql>
<!-- ==================== 3. select:查询 ==================== -->
<!-- 基础查询 -->
<select id="selectById" parameterType="long" resultMap="UserResultMap">
SELECT <include refid="userColumns"/>
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id
WHERE u.id = #{id}
</select>
<!-- 动态查询(使用 where + if)-->
<select id="selectByCondition" parameterType="map" resultMap="UserResultMap">
SELECT <include refid="userColumns"/>
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id
<where>
<if test="name != null and name != ''">
AND u.name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="minAge != null">
AND u.age >= #{minAge}
</if>
<if test="deptId != null">
AND d.id = #{deptId}
</if>
</where>
ORDER BY u.id
</select>
<!-- ==================== 4. insert:插入 ==================== -->
<!-- 普通插入(支持主键回填)-->
<insert id="insert" parameterType="com.example.entity.User"
useGeneratedKeys="true" keyProperty="id">
INSERT INTO users (name, age, dept_id)
VALUES (#{name}, #{age}, #{department.id})
</insert>
<!-- 批量插入(foreach)-->
<insert id="batchInsert" parameterType="list">
INSERT INTO users (name, age, dept_id) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age}, #{user.department.id})
</foreach>
</insert>
<!-- ==================== 5. update:更新 ==================== -->
<!-- 动态更新(set + if)-->
<update id="updateSelective" parameterType="com.example.entity.User">
UPDATE users
<set>
<if test="name != null and name != ''">
name = #{name},
</if>
<if test="age != null">
age = #{age},
</if>
<if test="department.id != null">
dept_id = #{department.id},
</if>
</set>
WHERE id = #{id}
</update>
<!-- ==================== 6. delete:删除 ==================== -->
<!-- 单条删除 -->
<delete id="deleteById" parameterType="long">
DELETE FROM users WHERE id = #{id}
</delete>
<!-- 批量删除(foreach)-->
<delete id="batchDelete" parameterType="list">
DELETE FROM users WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- ==================== 7. foreach:遍历集合(用于 IN、批量等)==================== -->
<!-- 示例:查询多个 ID 的用户 -->
<select id="selectByIds" parameterType="list" resultMap="UserResultMap">
SELECT <include refid="userColumns"/>
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id
WHERE u.id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</select>
<!-- ==================== 8. choose / when / otherwise:多条件分支(类似 switch)==================== -->
<select id="selectByPriority" parameterType="map" resultMap="UserResultMap">
SELECT <include refid="userColumns"/>
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id
<where>
<choose>
<when test="userId != null">
u.id = #{userId}
</when>
<when test="userName != null">
u.name = #{userName}
</when>
<otherwise>
u.age > 18
</otherwise>
</choose>
</where>
</select>
<!-- ==================== 9. trim:自定义前缀/后缀(where/set 的底层实现)==================== -->
<!-- 等价于 <where> 的手动写法 -->
<select id="selectWithTrim" parameterType="map" resultMap="UserResultMap">
SELECT <include refid="userColumns"/>
FROM users u
LEFT JOIN departments d ON u.dept_id = d.id
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">AND u.name = #{name}</if>
<if test="age != null">AND u.age = #{age}</if>
</trim>
</select>
<!-- ==================== 10. 调用存储过程(CALLABLE)==================== -->
<!-- 假设有一个存储过程:get_user_count_by_dept(IN dept_id INT, OUT total INT) -->
<select id="callGetUserCount" statementType="CALLABLE">
{ CALL get_user_count_by_dept(
#{deptId, mode=IN, jdbcType=INTEGER},
#{total, mode=OUT, jdbcType=INTEGER}
)
}
</select>
</mapper>Mybatis如何防止SQL注入
#{}是经过预编译的,参数值会被自动处理,不会直接拼接到sql语句中;${}是直接替换变量值,会把参数原样拼到sql中,会有注入问题
Mybatis动态SQL
<select id="findUsers" resultType="User">
SELECT * FROM users
WHERE 1=1
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age = #{age}
</if>
</select><select id="findUsersSmart" resultType="User">
SELECT * FROM users
<where>
<if test="name != null">name = #{name}</if>
<if test="age != null">AND age = #{age}</if>
<if test="email != null">OR email = #{email}</if>
</where>
</select><update id="updateUser">
UPDATE users
<set>
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
<if test="email != null">email = #{email},</if>
</set>
WHERE id = #{id}
</update><!-- 批量删除 -->
<delete id="deleteByIds">
DELETE FROM users WHERE id IN
<foreach collection="list" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</delete>
<!-- 批量插入 -->
<insert id="batchInsert">
INSERT INTO users (name, age) VALUES
<foreach collection="list" item="user" separator=",">
(#{user.name}, #{user.age})
</foreach>
</insert><!-- 等价于 <where> -->
<select id="findWithTrim" resultType="User">
SELECT * FROM users
<trim prefix="WHERE" prefixOverrides="AND |OR ">
<if test="name != null">AND name = #{name}</if>
<if test="age != null">OR age = #{age}</if>
</trim>
</select>
<!-- 等价于 <set> -->
<update id="updateWithTrim">
UPDATE users
<trim prefix="SET" suffixOverrides=",">
<if test="name != null">name = #{name},</if>
<if test="age != null">age = #{age},</if>
</trim>
WHERE id = #{id}
</update>从多个条件中只选一个执行(互斥)
<select id="searchUser" resultType="User">
SELECT * FROM users
<where>
<choose>
<when test="userId != null">
id = #{userId}
</when>
<when test="userName != null">
name = #{userName}
</when>
<when test="email != null">
email = #{email}
</when>
<otherwise>
status = 'ACTIVE'
</otherwise>
</choose>
</where>
</select><select id="findByNameLike" resultType="User">
<!-- 创建变量 pattern = '%' + name + '%' -->
<bind name="pattern" value="'%' + name + '%'" />
SELECT * FROM users
WHERE name LIKE #{pattern}
</select>Mybatis动态SQL是做什么的
根据不同条件动态生成SQL语句,解决静态SQL写死的问题,比如查询时条件可能有也可能没有,用动态sql就能灵活处理
mapper中如何传递多个参数
public interface UserMapper {
List<User> findUsers(
@Param("name") String name,
@Param("minAge") Integer minAge,
@Param("deptId") Long deptId
);
}public class UserQueryDTO {
private String name;
private Integer minAge;
private Long deptId;
// 必须有 getter/setter
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getMinAge() { return minAge; }
public void setMinAge(Integer minAge) { this.minAge = minAge; }
public Long getDeptId() { return deptId; }
public void setDeptId(Long deptId) { this.deptId = deptId; }
}
public interface UserMapper {
List<User> findUsers(UserQueryDTO query);
}public interface UserMapper {
List<User> findUsers(Map<String, Object> params);
}Mybatis-Plus
public interface UserMapper extends BaseMapper<User> { } // 无需声明基础 CRUD 方法!
SpringBoot
SpringBoot是什么
基于Spring的框架,简化Spring应用的搭建和开发过程;约定大于配置,不用手写大量配置文件
自动配置:数据库连接、web服务器、日志等
内嵌服务器:内嵌多种常用的web容器,tomcat等,可以直接打包运行
自动化依赖starter:简化依赖管理
项目怎么用SpringBoot
启动:写启动类,@SpringBootApplication注解,直接run
依赖管理:用maven,直接引入springboot提供的starter
controller:@RestController注解,定义路径和请求方式等,接受参数返回json对象
数据层:mybatis-plus
service:事务管理加@Transactional注解即可
配置:application.yml,包括数据库连接、服务端口、日志,大部分配置可以用注解
运行springboot方法
启动main方法
maven:mvn spring-boot:run
打包后使用java -jar 路径
springboot核心注解
@SpringBootApplication(=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan)
自动配置项目并扫描组件
springboot打包的jar包和普通的jar区别
springboot打包成的jar包可以直接运行java -jar xxx.jar,内嵌tomcat,但不能被其他项目依赖
普通jar包只是代码和类的集合,无法运行,主要作为第三方依赖