J2ee 考试复习

[toc]


前置概念(了解):

  1. Java EE(Jakarta EE)

Java EE(Java Enterprise Edition),现已更名为 Jakarta EE,是用于构建企业级应用程序的 Java 平台。它提供了一组 API 和运行时环境,支持开发和运行可扩展、分布式、多层的企业应用程序。

  1. Servlet

Servlet 是 Java EE 中的一个标准,主要用于处理 HTTP 请求。它是构建 Web 应用程序的基础组件。Servlet 运行在服务器端,可以生成动态的 Web 内容。

  1. JSP(JavaServer Pages)

JSP 是一种用于创建动态 Web 页面的技术,允许开发人员在 HTML 中嵌入 Java 代码。JSP 最终会被转换成 Servlet。

  1. JDBC(Java Database Connectivity)

JDBC 是 Java 访问数据库的标准 API。它提供了一组接口,允许 Java 应用程序连接到不同的数据库,并执行 SQL 查询和更新。

  1. JPA(Java Persistence API)

JPA 是一种 Java 规范,用于管理关系数据的持久化。它提供了对象关系映射(ORM)功能,允许开发人员使用面向对象的编程方式来操作数据库中的数据。

  1. EJB(Enterprise JavaBeans)

EJB 是一种用于构建可重用、分布式、事务性企业级应用程序的组件模型。EJB 组件运行在 EJB 容器中,提供事务管理、安全性、并发控制等服务。

  1. Spring 框架

Spring 是一个轻量级的 Java 开发框架,提供了全面的基础设施支持,特别是用于企业级应用开发。它的核心特性包括:

  • IoC(控制反转)和 DI(依赖注入):通过 Spring 容器管理对象的创建和依赖注入,减少了代码的耦合性。
  • AOP(面向切面编程):允许通过声明方式将横切关注点(如事务管理、日志记录)分离出来,提高代码的模块化。
  • 事务管理:提供了一致的编程模型和事务管理 API,支持声明式事务管理。
  • 数据访问:通过 Spring Data 简化数据访问层的开发,支持 JPA、JDBC、NoSQL 等多种数据访问技术。
  • MVC(Model-View-Controller):Spring MVC 框架用于构建 Web 应用程序,提供了灵活的路由、表单处理和视图解析机制。
  1. Maven/Gradle

Maven 和 Gradle 是两种常见的 Java 项目构建工具,支持依赖管理、构建、测试和部署。它们通过配置文件(如 pom.xmlbuild.gradle)定义项目结构和依赖关系。

  1. RESTful Web Services

REST(Representational State Transfer)是一种架构风格,用于构建基于 HTTP 的 Web 服务。Spring 提供了强大的支持来构建 RESTful Web 服务,包括通过 Spring MVC 构建 API、处理 JSON 和 XML 数据、以及跨域请求处理等。

  1. 安全性

Spring Security 是一个强大的、安全性框架,提供了全面的认证和授权服务,支持多种安全性策略,如基于角色的访问控制、方法级安全性和 URL 安全性。

  1. 单元测试

单元测试是确保代码质量的重要手段。JUnit 是 Java 中最流行的单元测试框架之一,Spring 提供了良好的支持来进行依赖注入和测试隔离。

通过理解和掌握以上概念,学习 Spring 框架将更加容易。这些知识点是企业级 Java 开发的基础,也是 Spring 框架设计和实现的核心思想。


绪论

什么是企业级应用

企业级应用程序是指满足企业需求的大规模软件系统,通常具有以下特点:

  • 高性能

    能够处理大量的并发请求,并保持稳定的响应时间。

  • 可靠性

    能够处理错误和异常情况,并保证数据的完整性和一致性。

  • 可扩展性

    能够根据需要增加或减少服务器的数量,以满足不断变化的业务需求。

  • 安全性

    能够保护敏感数据,并保证系统的安全性和可信度。

  • 可维护性

    能够方便地修改和维护代码,以适应不断变化的业务需求。

JavaWeb 开发的基本模型

纯 JSP 开发模型

​ 该模型即将所有业务处理,数据显示功能都有 JSP 完成。 这样 JSP 页面代码会非常混乱

JSP+JavaBean 开发模型(model1)

​ 这种模型将大多数业务处理功能交给 JavaBean 完成,而 JSP 主要完成数据显示功能。但该开发模式对于来自客户端的请求解析以及对于 JavaBean 对象的选择与创建,仍然有 Jsp 页面通过 Java 代码完成。所有 JSP 中还是有大量的业务处理功能

MVC 开发模型(model2)

MVC
  • M:Model 模型——承载数据,并对用户提交请求进行计算的模块
  • V:View 视图——为用户提供使用界面,与用户直接进行交互
  • C:controller 控制器——用于将用户请求转发给相应的 model 进行处理,并根据 model 的计算结果的用户提供相应的响应

三层架构开发模型

  • View:视图层——接收用户提交请求的代码在这里编写
  • service:业务层——系统的业务逻辑在这里完成
  • Dao:持久层——也称数据访问层(Data Access Object),直接操作数据库的代码在这里编写(jdbc)

为了更好的降低各层的耦合度,在三层架构程序中,采用面向接口编程,即上层对下层的调用,是通过接口实现的。而下层对上层的真正服务提供者,是下层接口实现类
这正是 依赖倒置原则 的一种体现

扩展

依赖倒置原则:

  1. 高层模块不应该依赖低层模块,二者都应该依赖其抽象。
  2. 抽象不应该依赖细节,细节应该依赖抽象。
  3. 依赖倒转的中心思想是面向接口编程。
  4. 依赖倒转的设计理念为:相对于细节的多变性,抽象的东西要稳定的多。以抽象的基础搭建的架构比以细节为基础的架构要稳定的多。在 java 中,抽象指的是接口或抽象类,细节就是具体的实现类。
  5. 使用接口或抽象类的目的是制定好规范,而不涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

MVC+三层架构开发模型

JavaWeb 开发的发展历程

参考资料:Java Web 开发发展简介 - 简书 (jianshu.com)

maven web 项目的结构特点

​ 在 idea 中新建项目,选择 webapp 模板快速开始,即可创建基于 maven 的 web 项目。

  1. Maven 项目的基础
    1. 项目的 Maven 坐标:
      1. GroupId:开发组的包名,一般以域名倒写,通常和项目的 java 包名对应。
      2. ArtificialId:项目 Id(名称)。
      3. Version:项目版本号
        1. 一般项目开发时期以小版本命名:0.0.1,0.0.2
        2. 在项目发布第一版正式版时,使用 1.0.0 开始命名
        3. 小版本更新修复 bug,更新最后一位修订号
        4. 新功能引入,大 bug 修复,修改第二位次要版本号
        5. 大改动,重构,api 调整等,更新第一位主版本号
    2. Maven 项目的配置
      1. 在根目录的 pom 文件中,存放着这个 Maven 项目的所有项目配置信息(不包含应用配置)
      2. 所有的依赖包都在 pom 文件的 dependency 中
  2. 基本项目结构
    1. 在项目的根目录中,有两个文件夹和一个文件:
    2. target:编译之后的 class 文件,资源文件,配置文件等,用于程序开发阶段的运行时。
    3. src:源代码目录,存放所有的项目源代码
      1. main:该目录存放所有的项目主代码
        1. java:存放 java 代码
        2. resources:存放配置文件,资源文件等
        3. webapp:web 应用的根目录,相当于传统 web 项目的 root 目录,这里存放了 web 项目的基本配置和入口
      2. test:该目录存放所有的测试代码,里面的目录结构和 main 是相同的
    4. pom.xml 文件:maven 项目的配置文件

重点: web.xml 是这个 web 应用的入口 ,所有的程序代码都在 src 目录中,target 为编译结果目录

Spring & AOP

Spring 是一个轻量级的开源框架,主要用于简化企业级应用开发。它的核心特性是面向对象编程中的 Inversion of Control (IoC) 和 Dependency Injection (DI),帮助开发者创建松耦合的、易于测试和维护的系统。

Spring 通过 IoC 和 DI 原则,提供了一种更加灵活和可维护的方式来构建企业级应用。它不仅降低了代码的耦合度,还提高了代码的可测试性和复用性,极大地简化了复杂系统的开发。Spring 框架的核心理念,使得它成为 Java 企业级开发中不可或缺的工具。

什么是 IOC 和 DI

IOC

  • Inversion of Control:控制反转

  • 控制反转是一种设计原则,它将对象的创建和依赖关系的管理交给了一个容器,而不是在应用程序中直接创建和管理对象。这意味着:

    • 传统方式:在传统的 Java 开发中,我们使用 new 关键字来创建对象。例如,当一个类需要依赖另一个类时,会在内部直接创建依赖对象。

    • 控制反转:在 IoC 的概念下,由容器来负责查找和注入依赖对象。对象本身不需要主动获取它所依赖的对象,而是被动地等待容器将依赖注入到它们中。

  • 什么是控制?为什么控制需要反转?

    • 根据传统 Java 项目开发,当我们需要一个对象来完成一件事时,我们通常会使用 new 关键字来创建这个类的实例对象,这种方式获取到的对象就是一种直接控制,是我们主动去创建对象从而控制他。

    • 这样的做法存在一些问题

      1. 难以复用这个对象。
      2. 创建对象时所需的依赖需要手动提供。
      3. 频繁的创建和销毁对象造成的性能损失。
    • 所以引入了控制反转的概念:

由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象

  • 什么东西被反转了?

    一个 对象的依赖对象的获取 被反转了

优势:

  • 松耦合:对象不直接依赖于其他对象,依赖关系通过容器管理,降低了类与类之间的耦合度。
  • 易测试:由于依赖可以被轻松替换为模拟对象,使单元测试更加容易。
  • 灵活性和可复用性:依赖关系的注入使得对象更容易复用和组合。

IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了 IoC 容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

其实 IoC 对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在 IoC/DI 思想中,应用程序就变成被动的了,被动的等待 IoC 容器来创建并注入它所需要的资源了。

IoC 很好的体现了面向对象设计法则之一—— 好莱坞法则:“别找我们,我们找你”;即由 IoC 容器帮对象找相应的依赖对象并注入,而不是由对象主动去找。

DI

  • Dependency Injection: 依赖注入
  • 在 IOC 的基础上,我们知道了控制需要反转,那么容器如何向对象 主动传递依赖 呢?这就是 DI 所做的事情

依赖注入是实现控制反转的一种方式。DI 通过在对象中注入它们所需要的依赖来实现 IoC。

实现方式:

  • 构造器注入:通过构造函数传递依赖。
  • Setter 注入:通过 Setter 方法传递依赖。
  • 接口注入:通过实现特定的接口传递依赖(这种方式较少使用)。

原理:

Spring 使用 Java 反射机制在运行时动态地创建对象和注入依赖。通过配置文件或注解,Spring 容器知道哪些类需要哪些依赖,并在创建这些类的实例时,将依赖注入进去。

DI 的不同方式和比较

构造器注入

特点:

  • 通过构造函数传递依赖。
  • 所有依赖在对象创建时必须提供。

优点:

  • 强制依赖注入:所有依赖必须在对象创建时提供,确保了对象的完整性。
  • 不可变性:依赖可以被声明为 final,保证了对象一旦创建,其依赖不可变。
  • 单一责任原则:对象创建和依赖注入在一个地方完成,代码更加简洁。

缺点:

  • 复杂构造函数:当依赖项较多时,构造函数参数会变得繁琐,不易阅读。
  • 灵活性低:不适用于依赖可以变化或是可选的场景。

示例:

public class Service {
    private final Repository repository;

    public Service(Repository repository) {
        this.repository = repository;
    }

    // 业务逻辑
}

Setter 注入

特点:

  • 通过 Setter 方法传递依赖。
  • 对象可以在创建后再设置其依赖。

优点:

  • 灵活性高:适用于依赖项可以变化或是可选的场景。
  • 清晰明了:依赖的设置方法清晰且易于阅读。

缺点:

  • 延迟初始化:对象创建后,依赖注入可能在稍后阶段进行,存在依赖未被注入的风险。
  • 缺乏强制性:不能强制依赖注入,可能导致对象不完整。
  • 不可变性差:不能将依赖声明为 final,可能导致对象的状态被改变。

示例:

public class Service {
    private Repository repository;

    public void setRepository(Repository repository) {
        this.repository = repository;
    }

    // 业务逻辑
}

字段注入

特点:

  • 通过直接设置字段来进行依赖注入。
  • 通常通过反射或者依赖注入容器来设置字段的值。

优点:

  • 简洁明了:依赖的设置直接在字段声明处,代码结构清晰。
  • 灵活性一般:依赖可以被动态修改,适用于某些需要动态变更依赖的场景。

缺点:

  • 耦合度高:依赖关系被硬编码在类的字段中,增加了类与类之间的耦合。
  • 不可变性差:字段一旦设置,可能被外部修改,导致对象状态的不可控性。

示例:

public class Service {
    
    @Autowired
    private Repository repository;
    
    // 或者
    @Resource
    private Repository repository;
    
    // 业务逻辑
}

接口注入

特点:

  • 通过实现特定的接口传递依赖。
  • 不常用,在实际开发中较少见。

优点:

  • 统一接口:所有依赖注入通过统一的接口进行,清晰且规范。

缺点:

  • 强耦合:类必须实现特定的接口,增加了类与类之间的耦合。
  • 不灵活:依赖注入方式较为固定,不够灵活。

示例:

public interface RepositoryAware {
    void setRepository(Repository repository);
}

public class Service implements RepositoryAware {
    private Repository repository;

    @Override
    public void setRepository(Repository repository) {
        this.repository = repository;
    }

    // 业务逻辑
}

比较

  • 构造器注入:适用于依赖关系固定且在对象创建时必须提供所有依赖的场景,保证了对象的不可变性和完整性。
  • Setter 注入:适用于依赖关系可以变化或是可选的场景,灵活性较高,但缺乏强制性和不可变性。
  • 接口注入:较少使用,适用于需要统一依赖注入方式的场景,增加了类与类之间的耦合,不够灵活。
    • 例如 Spring 提供的环境感知接口(Aware)通常使用这个方式
  • 字段注入:直接在字段声明处进行依赖注入,简洁明了,但耦合度高且不可变性差。

Bean 的配置方式(普通依赖和 bean 依赖示例)

基于 XML 的配置

特点:

  • 通过 XML 文件定义 bean 及其依赖关系。
  • 是 Spring 最早支持的配置方式。

优点:

  • 分离配置和代码:配置文件与代码分离,配置更直观清晰。
  • 集中管理:所有 bean 的定义集中在一个或几个 XML 文件中,便于管理和维护。

缺点:

  • 冗长繁琐:XML 文件较为冗长,不够简洁。
  • 不易维护:随着项目复杂度增加,XML 文件变得庞大,不易维护。

示例:

<!-- applicationContext.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="myBean" class="com.example.MyBean">
        <property name="name" value="John Doe" />
        <property name="age" value="30" />
    </bean>
    <bean id="repository" class="com.example.Repository" />
    <bean id="service" class="com.example.Service">
        <constructor-arg ref="repository" />
    </bean>
</beans>

基于注解的配置

特点:

  • 通过注解直接在代码中定义 bean 及其依赖关系。
  • 常用注解包括 @Component, @Service, @Repository, @Controller, @Autowired, @Qualifier, @Value, @Configuration 等。

优点:

  • 简洁明了:注解方式更为简洁,减少了 XML 配置的冗长。
  • 紧密结合:配置与代码紧密结合,更加直观。

缺点:

  • 配置分散:配置分散在各个类中,不如 XML 集中管理直观。
  • 耦合度增加:配置与业务代码耦合,可能影响代码的可移植性。

示例:

@Component
public class Repository {
    // 业务逻辑
}

@Component
public class Service {
    private final Repository repository;

    @Autowired  // 通过注解注入bean
    public Service(Repository repository) {
        this.repository = repository;
    }

    // 业务逻辑
}

@Component
public class MyBean {
    @Value("John Doe")
    private String name; 

    @Value("30")
    private int age;// 我是普通属性,从配置文件中获取值自动注入

    // Getters and setters
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }
}

<!-- applicationContext.xml -->
<context:component-scan base-package="com.example" />

基于 Java 配置(Java Configuration)

特点:

  • 使用 Java 类代替 XML 文件进行配置。
  • 通过 @Configuration@Bean 注解定义 bean。

优点:

  • 类型安全:Java 配置在编译时检查配置的正确性,避免了 XML 的类型错误。
  • 灵活性强:利用 Java 语言的特性,可以进行复杂的逻辑配置。
  • 可重构性:使用 IDE 的重构工具可以更容易地修改和管理配置。

缺点:

  • 学习曲线:需要了解 Spring 的 Java 配置方式。
  • 配置与代码结合:虽然配置集中在 Java 类中,但仍与业务代码有一定耦合。

示例:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConfig {

    @Bean // 声明了一个bean,在其他地方注入该类型的bean时时可以获取到这个bean
    public Repository repository() {
        return new Repository();
    }

    @Bean("service")// 命名bean,可以通过名称进行注入
    public Service service() {
        return new Service(repository());
    }
}

比较总结

  • 基于 XML 的配置:适用于需要分离配置与代码的项目,便于集中管理和维护,但 XML 文件可能会变得庞大和复杂。
  • 基于注解的配置:适用于小型项目或喜欢简洁配置的场景,配置与代码紧密结合,但可能导致配置分散和耦合增加。
  • 基于 Java 配置:适用于需要类型安全和灵活配置的项目,结合了配置的集中管理和 Java 语言的优势,但需要一定的学习成本。

容器中 Bean 的作用域

在 Spring 框架中,bean 的作用域决定了 bean 在容器中的生命周期和可见性。Spring 提供了多种作用域,以满足不同的需求。以下是 Spring 容器中 bean 的主要作用域及其详细介绍:

Singleton(单例) 默认作用域

  • 默认作用域:Spring 容器中的每个 bean 默认是单例的。
  • 特点:容器中只存在一个共享的 bean 实例,无论有多少次对该 bean 的请求,始终返回同一个实例。
  • 适用场景:适用于无状态的 bean 或需要共享状态的情况。

示例:

<bean id="myBean" class="com.example.MyBean" scope="singleton" />
@Component
@Scope("singleton")
public class MyBean {
    // Bean implementation
}

Prototype(原型)

  • 特点:每次请求 bean 时,都会创建一个新的实例。
  • 适用场景:适用于有状态的 bean,每个请求都需要一个独立的实例。

示例:

<bean id="myBean" class="com.example.MyBean" scope="prototype" />
@Component
@Scope("prototype")
public class MyBean {
    // Bean implementation
}

Request(请求)

  • 特点:每次 HTTP 请求都会创建一个新的 bean 实例。该作用域仅适用于 Web 应用。
  • 适用场景:适用于 Web 应用中,bean 实例需要与单个 HTTP 请求绑定的情况。
  • 例如: HttpServletRequest 和 Response,每次请求在业务代码中得到的对象都是不同的

示例:

<bean id="myBean" class="com.example.MyBean" scope="request" />
@Component
@Scope("request")
public class MyBean {
    // Bean implementation
}

Session(会话)

  • 特点:每个 HTTP 会话创建一个新的 bean 实例。该作用域仅适用于 Web 应用。
  • 适用场景:适用于 Web 应用中,bean 实例需要与用户会话绑定的情况。
  • 例如:HttpSession,在单次会话中是同一个 session 对象

示例:

<bean id="myBean" class="com.example.MyBean" scope="session" />
@Component
@Scope("session")
public class MyBean {
    // Bean implementation
}

Global Session(全局会话)

  • 特点:每个全局 HTTP 会话创建一个新的 bean 实例。主要用于 Portlet 应用环境。
  • 适用场景:适用于 Portlet 应用中,bean 实例需要与全局会话绑定的情况。

示例:

<bean id="myBean" class="com.example.MyBean" scope="globalSession" />
@Component
@Scope("globalSession")
public class MyBean {
    // Bean implementation
}

Application(应用)

  • 特点:每个 ServletContext 创建一个新的 bean 实例。该作用域适用于 Servlet 环境。
  • 适用场景:适用于需要在整个应用范围内共享的 bean 实例。

示例:

<bean id="myBean" class="com.example.MyBean" scope="application" />
@Component
@Scope("application")
public class MyBean {
    // Bean implementation
}

总结

  • Singleton:适用于无状态或需要共享状态的 bean。
  • Prototype:适用于有状态,每个请求需要独立实例的 bean。
  • Request:适用于 Web 应用中,每个 HTTP 请求需要独立实例的 bean。
  • Session:适用于 Web 应用中,每个用户会话需要独立实例的 bean。
  • Global Session:适用于 Portlet 应用中,每个全局会话需要独立实例的 bean。
  • Application:适用于需要在整个应用范围内共享的 bean。

根据应用的具体需求,选择适当的作用域来管理 bean 的生命周期和可见性,以优化应用性能和资源利用。

容器中 Bean 的生命周期

四个阶段:

  • 实例化 -> 属性赋值 -> 初始化 -> 销毁

简单描述

完整流程如下(了解一下就行):

详细过程

创建过程:

  • BeanNameAware.setBeanName() 在创建此 bean 的 bean 工厂中设置 bean 的名称,在普通属性设置之后调用,在 InitializinngBean.afterPropertiesSet()方法之前调用
  • BeanClassLoaderAware.setBeanClassLoader(): 在普通属性设置之后,InitializingBean.afterPropertiesSet()之前调用
  • BeanFactoryAware.setBeanFactory() : 回调提供了自己的 bean 实例工厂,在普通属性设置之后,在 InitializingBean.afterPropertiesSet()或者自定义初始化方法之前调用
  • EnvironmentAware.setEnvironment(): 设置 environment 在组件使用时调用
  • EmbeddedValueResolverAware.setEmbeddedValueResolver(): 设置 StringValueResolver 用来解决嵌入式的值域问题
  • ResourceLoaderAware.setResourceLoader(): 在普通 bean 对象之后调用,在 afterPropertiesSet 或者自定义的 init-method 之前调用,在 ApplicationContextAware 之前调用。
  • ApplicationEventPublisherAware.setApplicationEventPublisher(): 在普通 bean 属性之后调用,在初始化调用 afterPropertiesSet 或者自定义初始化方法之前调用。在 ApplicationContextAware 之前调用。
  • MessageSourceAware.setMessageSource(): 在普通 bean 属性之后调用,在初始化调用 afterPropertiesSet 或者自定义初始化方法之前调用,在 ApplicationContextAware 之前调用。
  • ApplicationContextAware.setApplicationContext(): 在普通 Bean 对象生成之后调用,在 InitializingBean.afterPropertiesSet 之前调用或者用户自定义初始化方法之前。在 ResourceLoaderAware.setResourceLoader,ApplicationEventPublisherAware.setApplicationEventPublisher,MessageSourceAware 之后调用。
  • ServletContextAware.setServletContext(): 运行时设置 ServletContext,在普通 bean 初始化后调用,在 InitializingBean.afterPropertiesSet 之前调用,在 ApplicationContextAware 之后调用注:是在 WebApplicationContext 运行时
  • BeanPostProcessor.postProcessBeforeInitialization() : 将此 BeanPostProcessor 应用于给定的新 bean 实例 在任何 bean 初始化回调方法(像是 InitializingBean.afterPropertiesSet 或者自定义的初始化方法)之前调用。这个 bean 将要准备填充属性的值。返回的 bean 示例可能被普通对象包装,默认实现返回是一个 bean。
  • BeanPostProcessor.postProcessAfterInitialization() : 将此 BeanPostProcessor 应用于给定的新 bean 实例 在任何 bean 初始化回调方法(像是 InitializingBean.afterPropertiesSet 或者自定义的初始化方法)之后调用。这个 bean 将要准备填充属性的值。返回的 bean 示例可能被普通对象包装
  • InitializingBean.afterPropertiesSet(): 被 BeanFactory 在设置所有 bean 属性之后调用(并且满足 BeanFactory 和 ApplicationContextAware)。 销毁:
  • DestructionAwareBeanPostProcessor.postProcessBeforeDestruction(): 在销毁之前将此 BeanPostProcessor 应用于给定的 bean 实例。能够调用自定义回调,像是 DisposableBean 的销毁和自定义销毁方法,这个回调仅仅适用于工厂中的单例 bean(包括内部 bean)
  • 实现了自定义的 destory()方法

Spring 项目开发步骤

注意,spring 是一个实现了 ioc 和 di 的容器化框架,spring 项目开发就是配置容器,并从中获得 bean

导入 Spring 开发的基本包坐标

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>6.1.3</version>
    </dependency>

建立容器配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <!-- 启用基于注解的bean扫描 -->
    <context:component-scan base-package="online.zust.qcqcqc.j2eestudy" />

</beans>

从容器中获得 Bean

这里配置了一个 UserDao 的 bean 来进行演示:

@Test
public void test(){
    ApplicationContext applicationContext = new  
        ClassPathXmlApplicationContext("applicationContext.xml");
    UserDao userDao = (UserDao) applicationContext.getBean("userDao");   				 userDao.save();
}

AOP 的基本概念与特点

AOP(Aspect-Oriented Programming,面向切面编程)是 Spring 框架中非常重要的特性之一。它提供了一种在不修改业务代码的情况下将横切关注点(如日志记录、安全、事务管理等)分离出来的机制。AOP 的基本特点如下:

横切关注点

定义

  • 横切关注点:是指那些影响到应用程序多个模块的功能,如日志记录、安全检查、事务管理等。这些功能通常分散在应用程序的各个部分,导致代码重复和难以维护。

特点

  • 独立于业务逻辑:横切关注点与业务逻辑分离,独立管理。
  • 集中管理:通过 AOP 可以集中管理横切关注点,减少重复代码,提高代码的可维护性。

织入

定义

  • 织入:是指将横切关注点代码(即切面)和业务代码结合在一起的过程。织入可以在编译时、类加载时或运行时进行。

特点

  • 编译时织入:在编译阶段将切面代码织入目标类。需要特定的编译器支持,如 AspectJ。
  • 类加载时织入:在类加载阶段通过类加载器将切面织入。使用 Java 的代理机制实现。
  • 运行时织入:在运行时通过动态代理将切面织入。Spring AOP 采用这种方式,基于 JDK 动态代理或 CGLIB 实现。

切面(Aspect)

定义

  • 切面:是横切关注点的模块化体现,一个切面可以包含多个通知(Advice)和切点(Pointcut)。

特点

  • 模块化:切面将横切关注点模块化,一个切面可以管理多个通知。
  • 可复用:切面可以应用于多个目标对象,具有很高的复用性。

通知(Advice)

定义

  • 通知:是指切面中定义的具体的横切关注点代码,即要执行的动作。通知可以在目标方法执行的不同阶段进行织入。

类型

  • 前置通知(Before Advice):在目标方法执行前执行。
  • 后置通知(After Advice):在目标方法执行后执行。
  • 返回通知(After Returning Advice):在目标方法成功返回后执行。
  • 异常通知(After Throwing Advice):在目标方法抛出异常后执行。
  • 环绕通知(Around Advice):在目标方法执行前后都可以执行,自定义增强逻辑。

切点(Pointcut)

定义

  • 切点:是指匹配连接点的断言,定义了哪些连接点需要执行通知。切点表达式通常使用 AspectJ 的语法来定义。

特点

  • 灵活性:切点可以通过各种条件(如方法名、参数类型、注解等)灵活匹配连接点。
  • 精确控制:切点表达式可以精确控制通知应用的范围,避免不必要的通知执行。

连接点(Join Point)

定义

  • 连接点:是程序执行过程中的某个点,如方法调用或异常抛出。通知可以在这些点进行织入。

特点

  • 多样性:在 Spring AOP 中,连接点主要是方法调用。
  • 扩展性:理论上连接点可以是任何执行点,如字段访问、构造函数调用等,但 Spring AOP 主要支持方法级别的连接点。

引入(Introduction)

定义

  • 引入:允许在不修改目标类的情况下向其添加新的方法或属性。通过引入,可以为现有类增加新的接口实现。

特点

  • 增强现有类:引入可以为现有类增加新的功能,而无需修改其源码。
  • 接口实现:通过引入可以为目标类增加新的接口及其实现。

目标对象(Target Object)

定义

  • 目标对象:是被一个或多个切面所通知的对象。Spring AOP 中的目标对象通常是被代理的对象。

特点

  • 被代理对象:目标对象通常是通过代理模式被切面增强的对象。
  • 透明性:目标对象对切面的存在是透明的,它们不需要知道自身被增强。

总结

AOP 通过将横切关注点(如日志记录、安全检查等)分离出来,并通过切面模块化和织入技术将其与业务代码结合,实现了代码的解耦和重用。AOP 的基本特点包括横切关注点、织入、切面、通知、切点、连接点、引入和目标对象,这些概念共同构成了 AOP 的核心机制,使得 Spring 框架在处理复杂业务逻辑时更加灵活和高效。

SpringAOP 增强处理的类型及特点

Spring AOP 提供了多种类型的增强处理(Advice),每种类型在目标方法执行的不同阶段应用,从而实现横切关注点(如日志记录、事务管理等)。下面是 Spring AOP 增强处理的主要类型及其特点:

前置通知(Before Advice)

定义

  • 前置通知 是在目标方法执行前运行的通知。

特点

  • 执行时机:在目标方法执行之前执行。
  • 用途:通常用于验证、日志记录、权限检查等操作。

示例

@Aspect
@Component
public class LoggingAspect {
    
    @Before("execution(* com.example.service.*.*(..))")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }
}

后置通知(After Advice)

定义

  • 后置通知 是在目标方法执行之后运行的通知,无论方法是正常返回还是抛出异常。

特点

  • 执行时机:在目标方法执行之后执行,无论方法是否成功返回。
  • 用途:通常用于资源清理、日志记录等操作。

示例

@Aspect
@Component
public class LoggingAspect {
    
    @After("execution(* com.example.service.*.*(..))")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }
}

返回通知(After Returning Advice)

定义

  • 返回通知 是在目标方法正常返回后运行的通知。

特点

  • 执行时机:在目标方法成功返回之后执行。
  • 用途:通常用于获取返回值进行日志记录、结果处理等操作。

示例

@Aspect
@Component
public class LoggingAspect {
    
    @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        System.out.println("After Returning method: " + joinPoint.getSignature().getName() + ", Result: " + result);
    }
}

异常通知(After Throwing Advice)

定义

  • 异常通知 是在目标方法抛出异常后运行的通知。

特点

  • 执行时机:在目标方法抛出异常之后执行。
  • 用途:通常用于异常处理、错误日志记录等操作。

示例

@Aspect
@Component
public class LoggingAspect {
    
    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
        System.out.println("After Throwing method: " + joinPoint.getSignature().getName() + ", Exception: " + error);
    }
}

环绕通知(Around Advice)

定义

  • 环绕通知 是在目标方法执行前后都可以执行的通知,能够完全控制目标方法的执行。

特点

  • 执行时机:在目标方法执行前后都可以执行。
  • 用途:适用于复杂的场景,例如事务管理、性能监控等。
  • 控制权:可以完全控制目标方法的执行,包括决定是否执行目标方法。

示例

@Aspect
@Component
public class LoggingAspect {
    
    @Around("execution(* com.example.service.*.*(..))")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("Around before method: " + joinPoint.getSignature().getName());
        Object result = joinPoint.proceed(); // 执行目标方法
        System.out.println("Around after method: " + joinPoint.getSignature().getName());
        return result;
    }
}

总结

Spring AOP 提供了多种类型的增强处理,每种类型在目标方法执行的不同阶段应用:

  • 前置通知(Before Advice):在目标方法执行前执行,用于预处理,如日志记录、权限检查等。
  • 后置通知(After Advice):在目标方法执行后执行,无论方法是否成功返回,用于资源清理、日志记录等。
  • 返回通知(After Returning Advice):在目标方法成功返回后执行,用于处理返回值、日志记录等。
  • 异常通知(After Throwing Advice):在目标方法抛出异常后执行,用于异常处理、错误日志记录等。
  • 环绕通知(Around Advice):在目标方法执行前后都执行,能够完全控制目标方法的执行,用于复杂场景,如事务管理、性能监控等。

通过这些增强处理类型,Spring AOP 可以灵活地管理和分离横切关注点,使业务逻辑更加清晰和可维护。

SpringAOP 切入点表达式

Spring AOP 切入点表达式用于定义哪些方法应该被通知(advised)。切入点表达式的语法基于 AspectJ 切入点语言,它非常强大且灵活。以下是关于 Spring AOP 切入点表达式的详细解释:

切入点表达式的基本语法

切入点表达式的基本格式如下:

@Pointcut("expression")
public void pointcutName() {}

expression 是具体的切入点表达式,用于匹配目标方法。常见的切入点表达式有 executionwithinthistargetargs@annotation

常见的切入点表达式类型及示例

  1. execution

    execution 是最常用的切入点表达式,用于匹配方法执行。

    // 匹配 com.example.service 包中所有类的所有方法
    @Pointcut("execution(* com.example.service..*(..))")
    public void serviceMethods() {}
    
    // 匹配所有返回类型为 void 的方法
    @Pointcut("execution(void *(..))")
    public void voidMethods() {}
    
    // 匹配 com.example.service.UserService 中的所有方法
    @Pointcut("execution(* com.example.service.UserService.*(..))")
    public void userServiceMethods() {}
    
  2. within

    within 用于匹配指定类型内的方法。

    // 匹配 com.example.service 包中所有类的所有方法
    @Pointcut("within(com.example.service..*)")
    public void serviceMethods() {}
    
    // 匹配 com.example.service.UserService 类中的所有方法
    @Pointcut("within(com.example.service.UserService)")
    public void userServiceMethods() {}
    
  3. this

    this 用于匹配当前 AOP 代理对象的类型。

    // 匹配代理对象实现了 UserService 接口的所有方法
    @Pointcut("this(com.example.service.UserService)")
    public void proxyImplementsUserService() {}
    
  4. target

    target 用于匹配目标对象的类型。

    // 匹配目标对象实现了 UserService 接口的所有方法
    @Pointcut("target(com.example.service.UserService)")
    public void targetImplementsUserService() {}
    
  5. args

    args 用于匹配方法的参数类型。

    // 匹配第一个参数为 Long 类型的方法
    @Pointcut("args(java.lang.Long,..)")
    public void methodsWithFirstArgLong() {}
    
  6. @annotation

    @annotation 用于匹配方法上具有指定注解的方法。

    // 匹配方法上具有 @Transactional 注解的方法
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}
    

切入点表达式示例

示例 1:日志记录

在目标方法执行前后记录日志:

@Aspect
@Component
public class LoggingAspect {
    
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void serviceMethods() {}
    
    @Before("serviceMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }
    
    @After("serviceMethods()")
    public void logAfter(JoinPoint joinPoint) {
        System.out.println("After method: " + joinPoint.getSignature().getName());
    }
}
示例 2:事务管理

在具有 @Transactional 注解的方法上开启和提交事务:

@Aspect
@Component
public class TransactionAspect {
    
    @Pointcut("@annotation(org.springframework.transaction.annotation.Transactional)")
    public void transactionalMethods() {}
    
    @Around("transactionalMethods()")
    public Object manageTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
        // 开启事务
        System.out.println("Starting transaction");
        try {
            Object result = joinPoint.proceed();
            // 提交事务
            System.out.println("Committing transaction");
            return result;
        } catch (Throwable throwable) {
            // 回滚事务
            System.out.println("Rolling back transaction");
            throw throwable;
        }
    }
}

组合切入点表达式

切入点表达式可以通过逻辑运算符进行组合,以匹配更复杂的条件:

  • AND:使用 && 组合
  • OR:使用 || 组合
  • NOT:使用 ! 进行取反
示例
@Aspect
@Component
public class CombinedAspect {
    
    // 匹配 com.example.service 包中所有类的方法或具有 @Transactional 注解的方法
    @Pointcut("execution(* com.example.service..*(..)) || @annotation(org.springframework.transaction.annotation.Transactional)")
    public void serviceOrTransactionalMethods() {}
    
    @Before("serviceOrTransactionalMethods()")
    public void logBefore(JoinPoint joinPoint) {
        System.out.println("Before method: " + joinPoint.getSignature().getName());
    }
}

总结

Spring AOP 切入点表达式通过丰富的语法提供了灵活的方式来匹配目标方法。这使得开发人员可以轻松地定义哪些方法需要增强处理,进而实现日志记录、事务管理等横切关注点的分离和集中管理。

MyBatis/JPA/Hibernate

对象关系映射(Object-Relational Mapping,简称 ORM)是一种用于实现面向对象编程与关系数据库之间数据转换的技术。它的基本原理是通过描述对象与数据库表之间的映射关系,自动将对象的状态与数据库中的记录进行同步。以下是 ORM 的基本原理和主要概念:

ORM 的基本原理

映射(Mapping)

映射是 ORM 的核心概念,指的是在程序中的类(对象)与数据库中的表(关系)之间建立对应关系。主要包括:

  • 类与表的映射:一个类对应数据库中的一张表。
  • 属性与字段的映射:类的每个属性对应表中的一个字段。
  • 对象与记录的映射:类的一个实例(对象)对应表中的一条记录。

实体类(Entity Class)

实体类是一个普通的 Java 类,通常被注解或 XML 配置标记为持久化实体。它的实例表示数据库表中的一条记录。例如:

@Entity
@Table(name = "users")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "username")
    private String username;

    @Column(name = "password")
    private String password;

    // Getters and setters
}

主键(Primary Key)

每个实体类都需要有一个主键字段,通常用来唯一标识表中的一条记录。主键可以是单个字段或多个字段的组合。

MyBatis 的基本原理

MyBatis 是一种持久层框架,用于简化 Java 应用程序与数据库之间的交互。与全自动的 ORM(如 Hibernate)不同,MyBatis 更注重 SQL 的灵活性和控制权,开发者可以直接编写 SQL 语句,并通过映射文件将 SQL 语句与 Java 对象进行绑定。下面是 MyBatis 的基本原理和核心概念:

  1. 配置文件

MyBatis 的核心配置文件通常命名为 mybatis-config.xml,用于配置 MyBatis 的运行环境,包括数据库连接、全局设置、别名等。

  1. 映射文件

映射文件通常与具体的 Mapper 接口对应,例如 UserMapper.xml,用于定义 SQL 语句和结果映射。

  1. Mapper 接口

Mapper 接口定义了与数据库交互的方法,与映射文件中的 SQL 语句相对应。

  1. 实体类

实体类对应数据库中的表,通常是一个普通的 Java 类。

  1. MyBatis SqlSession

SqlSession 是 MyBatis 执行 SQL 语句的关键对象,提供了方法来执行映射的 SQL 语句。

Mybatis 的工作流程

  1. 获取 sqlSessionFactory 对象
  2. 获取 sqlSession 对象
  3. 获取 mapper 接口的代理对象(MapperProxy)
  4. 执行增删改查方法
// 配置文件路径
String resource = "mybatis-config.xml";
// 获取输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
// 通过SqlSessionFactoryBuilder来构建SqlSessionFactory(工厂模式)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 打开Sqlsession
sqlSession = sqlSessionFactory.openSession();
// 通过sqlsession获取mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 调用mapper接口的方法,进行数据库操作
List<User> userList = userMapper.selectAllUsers();
// 事务结束
sqlSession.rollback(); // 回退事务
// sqlSession.commit(); // 提交事务
// 在一次事务中,提交或者回退只能执行其中一个
sqlSession.close(); // 关闭sqlsession

下面是扩展阅读:

MyBatis 接口方法 SQL 映射: XML 和注解

在 MyBatis 中,Mapper 接口的方法可以通过 XML 配置或注解来映射到具体的 SQL 语句。这两种方式各有优缺点,适用于不同的场景。

XML 方式

使用 XML 文件来配置 SQL 语句,可以将 SQL 和 Java 代码分离,便于维护和管理。下面是通过 XML 配置 Mapper 的方法映射 SQL 语句的示例。

示例:UserMapper.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="com.example.mapper.UserMapper">

  <resultMap id="userResultMap" type="com.example.model.User">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="password" column="password"/>
  </resultMap>

  <select id="selectUserById" parameterType="long" resultMap="userResultMap">
    SELECT * FROM users WHERE id = #{id}
  </select>

  <insert id="insertUser" parameterType="com.example.model.User">
    INSERT INTO users (username, password) VALUES (#{username}, #{password})
  </insert>

  <update id="updateUser" parameterType="com.example.model.User">
    UPDATE users SET username = #{username}, password = #{password} WHERE id = #{id}
  </update>

  <delete id="deleteUser" parameterType="long">
    DELETE FROM users WHERE id = #{id}
  </delete>

</mapper>

示例:UserMapper.java

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface UserMapper {
    User selectUserById(Long id);
    void insertUser(User user);
    void updateUser(User user);
    void deleteUser(Long id);
}

注解方式

使用注解方式,可以将 SQL 语句直接写在 Mapper 接口的方法上,减少了 XML 配置文件的数量,使得配置更加直观和简洁。

示例:UserMapper.java

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.*;

import java.util.List;

public interface UserMapper {

    @Select("SELECT * FROM users WHERE id = #{id}")
    @Results(id = "userResultMap", value = {
        @Result(property = "id", column = "id", id = true),
        @Result(property = "username", column = "username"),
        @Result(property = "password", column = "password")
    })
    User selectUserById(Long id);

    @Insert("INSERT INTO users (username, password) VALUES (#{username}, #{password})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insertUser(User user);

    @Update("UPDATE users SET username = #{username}, password = #{password} WHERE id = #{id}")
    void updateUser(User user);

    @Delete("DELETE FROM users WHERE id = #{id}")
    void deleteUser(Long id);
}

比较:XML 方式和注解方式

XML 方式的优点:

  1. 分离关注点:SQL 语句和 Java 代码分离,便于维护和管理。
  2. 支持复杂 SQL:对于复杂的 SQL 查询和动态 SQL,XML 提供了更强的支持。
  3. 便于版本控制:SQL 语句在 XML 中,修改时更容易进行版本控制。

XML 方式的缺点:

  1. 冗长:对于简单的查询,XML 配置显得过于冗长。
  2. 分散:SQL 语句和 Java 代码分离,可能需要在多个文件中查找和修改代码。

注解方式的优点:

  1. 简洁:对于简单的 CRUD 操作,注解方式更为简洁和直观。
  2. 集中:SQL 语句直接在 Java 代码中,减少了文件数量,便于快速查找和修改。

注解方式的缺点:

  1. 复杂性限制:对于复杂的查询和动态 SQL,注解方式支持有限,不如 XML 灵活。
  2. 代码耦合:SQL 语句与 Java 代码耦合,可能影响代码的可读性和可维护性。

总结

MyBatis 提供了两种方式来映射 SQL 语句到 Mapper 接口的方法:XML 方式和注解方式。XML 方式适用于复杂的 SQL 查询和动态 SQL,强调分离关注点;注解方式则更适用于简单的 CRUD 操作,强调简洁和直观。实际开发中,可以根据具体需求选择适合的方式,或者混合使用两种方式,以发挥各自的优势。

MyBatis 接口参数

  • 为什么在多个参数时需要使用@Param 注解?

    在 mybatis 中默认使用 jdk 的 Proxy 代理,无法获取到接口方法签名中的参数名(cglib 可以),所以在参数不唯一,也就是无法确定每个参数的具体对应关系时,一定要加上@Param 注解,否则会爆出 BinddingException(接口绑定异常)

在 MyBatis 中,接口方法的参数可以通过多种方式传递到 SQL 语句中。常见的参数传递方式包括单个参数、多参数、对象参数和使用 @Param 注解。以下是这些不同参数传递方式的详细说明和示例。

单个参数

当接口方法只有一个参数时,MyBatis 会直接将该参数传递到 SQL 语句中,可以使用 #{} 占位符引用参数。

示例

// Mapper接口
public interface UserMapper {
    User selectUserById(Long id);
}

// XML映射文件
<select id="selectUserById" parameterType="long" resultType="com.example.model.User">
    SELECT * FROM users WHERE id = #{id}
</select>

多参数

当接口方法有多个参数时,可以使用 @Param 注解为每个参数指定一个名称,这样在 SQL 语句中可以通过名称引用这些参数。

示例

// Mapper接口
public interface UserMapper {
    User selectUserByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
}

// XML映射文件
<select id="selectUserByUsernameAndPassword" resultType="com.example.model.User">
    SELECT * FROM users WHERE username = #{username} AND password = #{password}
</select>

对象参数

当接口方法传递一个对象作为参数时,可以直接在 SQL 语句中引用对象的属性。

示例

// 实体类
public class User {
    private String username;
    private String password;
    // Getters and setters
}

// Mapper接口
public interface UserMapper {
    User selectUserByUser(User user);
}

// XML映射文件
<select id="selectUserByUser" parameterType="com.example.model.User" resultType="com.example.model.User">
    SELECT * FROM users WHERE username = #{username} AND password = #{password}
</select>

Map 参数

使用 Map 作为参数时,可以通过键名引用 Map 中的值。

示例

// Mapper接口
public interface UserMapper {
    User selectUserByMap(Map<String, Object> paramMap);
}

// XML映射文件
<select id="selectUserByMap" parameterType="map" resultType="com.example.model.User">
    SELECT * FROM users WHERE username = #{username} AND password = #{password}
</select>

// 调用示例
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("username", "user1");
paramMap.put("password", "pass1");
User user = userMapper.selectUserByMap(paramMap);

使用 @Param 注解

@Param 注解不仅适用于多参数方法,还可以用于重命名参数,以便在 SQL 语句中引用时更具可读性。

示例

// Mapper接口
public interface UserMapper {
    List<User> selectUsersByStatusAndAge(@Param("status") String status, @Param("age") int age);
}

// XML映射文件
<select id="selectUsersByStatusAndAge" resultType="com.example.model.User">
    SELECT * FROM users WHERE status = #{status} AND age = #{age}
</select>

注解方式传递参数

在使用注解方式定义 SQL 语句时,也可以直接使用参数和 @Param 注解。

示例

// Mapper接口
public interface UserMapper {
    @Select("SELECT * FROM users WHERE username = #{username} AND password = #{password}")
    User selectUserByUsernameAndPassword(@Param("username") String username, @Param("password") String password);
}

复杂类型参数

当方法参数是一个复杂类型(如对象或集合)时,MyBatis 也可以处理这些参数。

示例

// Mapper接口
public interface UserMapper {
    List<User> selectUsersByIds(@Param("ids") List<Long> ids);
}

// XML映射文件
<select id="selectUsersByIds" resultType="com.example.model.User">
    SELECT * FROM users WHERE id IN
    <foreach item="id" collection="ids" open="(" separator="," close=")">
        #{id}
    </foreach>
</select>

// 调用示例
List<Long> ids = Arrays.asList(1L, 2L, 3L);
List<User> users = userMapper.selectUsersByIds(ids);

总结

MyBatis 提供了多种方式来传递接口方法的参数,包括单个参数、多参数、对象参数、Map 参数和使用 @Param 注解。选择哪种方式取决于具体的业务需求和代码的可读性。通过合理使用这些参数传递方式,可以提高代码的简洁性和可维护性。

Mybatis 关联配置:对象关联,集合关联

MyBatis 支持对象关联和集合关联配置,用于处理数据库表之间的关系映射。在实际开发中,经常会遇到一对一、一对多、多对多等关系,MyBatis 提供了多种方式来处理这些关系。以下是关于对象关联和集合关联的详细说明及示例。

一对一关联(对象关联)

一对一关联表示一个实体类包含另一个实体类的引用。可以通过 <association> 元素来配置这种关联。

示例

假设有两个表:usersaddresses,每个用户有一个地址。

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50),
    address_id BIGINT,
    FOREIGN KEY (address_id) REFERENCES addresses(id)
);

CREATE TABLE addresses (
    id BIGINT PRIMARY KEY,
    street VARCHAR(100),
    city VARCHAR(50),
    state VARCHAR(50)
);

实体类

public class User {
    private Long id;
    private String username;
    private Address address;
    // Getters and setters
}

public class Address {
    private Long id;
    private String street;
    private String city;
    private String state;
    // Getters and setters
}

Mapper 接口

public interface UserMapper {
    User selectUserById(Long id);
}

XML 映射文件

<select id="selectUserById" resultMap="userResultMap">
    SELECT u.id AS u_id, u.username, a.id AS a_id, a.street, a.city, a.state
    FROM users u
    LEFT JOIN addresses a ON u.address_id = a.id
    WHERE u.id = #{id}
</select>

<resultMap id="userResultMap" type="com.example.model.User">
    <id property="id" column="u_id"/>
    <result property="username" column="username"/>
    <association property="address" javaType="com.example.model.Address">
        <id property="id" column="a_id"/>
        <result property="street" column="street"/>
        <result property="city" column="city"/>
        <result property="state" column="state"/>
    </association>
</resultMap>

一对多关联(集合关联)

一对多关联表示一个实体类包含另一个实体类的集合。可以通过 <collection> 元素来配置这种关联。

示例

假设有两个表:usersorders,每个用户有多个订单。

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    username VARCHAR(50)
);

CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT,
    order_number VARCHAR(50),
    FOREIGN KEY (user_id) REFERENCES users(id)
);

实体类

public class User {
    private Long id;
    private String username;
    private List<Order> orders;
    // Getters and setters
}

public class Order {
    private Long id;
    private String orderNumber;
    // Getters and setters
}

Mapper 接口

public interface UserMapper {
    User selectUserWithOrdersById(Long id);
}

XML 映射文件

<select id="selectUserWithOrdersById" resultMap="userResultMap">
    SELECT u.id AS u_id, u.username, o.id AS o_id, o.order_number
    FROM users u
    LEFT JOIN orders o ON u.id = o.user_id
    WHERE u.id = #{id}
</select>

<resultMap id="userResultMap" type="com.example.model.User">
    <id property="id" column="u_id"/>
    <result property="username" column="username"/>
    <collection property="orders" ofType="com.example.model.Order">
        <id property="id" column="o_id"/>
        <result property="orderNumber" column="order_number"/>
    </collection>
</resultMap>

多对多关联

多对多关联通常通过中间表来实现,MyBatis 可以通过多个关联来处理这种关系。

示例

假设有三个表:studentscoursesstudent_courses,表示学生和课程的多对多关系。

CREATE TABLE students (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50)
);

CREATE TABLE courses (
    id BIGINT PRIMARY KEY,
    name VARCHAR(50)
);

CREATE TABLE student_courses (
    student_id BIGINT,
    course_id BIGINT,
    FOREIGN KEY (student_id) REFERENCES students(id),
    FOREIGN KEY (course_id) REFERENCES courses(id)
);

实体类

public class Student {
    private Long id;
    private String name;
    private List<Course> courses;
    // Getters and setters
}

public class Course {
    private Long id;
    private String name;
    // Getters and setters
}

Mapper 接口

public interface StudentMapper {
    Student selectStudentWithCoursesById(Long id);
}

XML 映射文件

<select id="selectStudentWithCoursesById" resultMap="studentResultMap">
    SELECT s.id AS s_id, s.name AS s_name, c.id AS c_id, c.name AS c_name
    FROM students s
    LEFT JOIN student_courses sc ON s.id = sc.student_id
    LEFT JOIN courses c ON sc.course_id = c.id
    WHERE s.id = #{id}
</select>

<resultMap id="studentResultMap" type="com.example.model.Student">
    <id property="id" column="s_id"/>
    <result property="name" column="s_name"/>
    <collection property="courses" ofType="com.example.model.Course">
        <id property="id" column="c_id"/>
        <result property="name" column="c_name"/>
    </collection>
</resultMap>

总结

MyBatis 通过 <association><collection> 元素支持一对一和一对多的对象关联配置。对于多对多关联,可以通过多个关联来实现。通过合理配置这些关联,可以方便地将关系数据库中的数据映射到 Java 对象中,简化数据访问层的开发。

在使用 MyBatis 进行项目开发时,通常遵循以下步骤来完成整个开发过程。这些步骤包括环境配置、实体类和 Mapper 接口的创建、SQL 映射文件的编写、配置文件的设置以及应用程序的测试和运行。下面是一个详细的开发步骤指南:

MyBatis 项目开发步骤

环境配置

引入 MyBatis 依赖

在 Maven 项目的 pom.xml 中添加 MyBatis 及其所需的依赖项:

<dependencies>
    <!-- MyBatis依赖 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>

    <!-- MyBatis-Spring依赖(如果使用Spring整合) -->
    <dependency>
        <groupId>org.mybatis.spring</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>

    <!-- 数据库连接池,如HikariCP -->
    <dependency>
        <groupId>com.zaxxer</groupId>
        <artifactId>HikariCP</artifactId>
        <version>4.0.3</version>
    </dependency>

    <!-- JDBC驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.23</version>
    </dependency>
</dependencies>

创建数据库和表

根据业务需求创建数据库和表。例如:

CREATE DATABASE mybatis_example;

USE mybatis_example;

CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50),
    password VARCHAR(50)
);

配置文件

MyBatis 配置文件(mybatis-config.xml)

创建 MyBatis 的配置文件 mybatis-config.xml,用于配置全局设置、数据源、事务管理等。

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis_example?useSSL=false&amp;serverTimezone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="password"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/example/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

创建实体类

创建与数据库表对应的实体类,例如 User 类。

package com.example.model;

public class User {
    private Long id;
    private String username;
    private String password;

    // Getters and Setters
}

创建 Mapper 接口

创建 Mapper 接口,并定义数据库操作方法。例如 UserMapper 接口。

package com.example.mapper;

import com.example.model.User;
import org.apache.ibatis.annotations.Param;
import java.util.List;

public interface UserMapper {
    User selectUserById(Long id);
    List<User> selectAllUsers();
    void insertUser(User user);
    void updateUser(User user);
    void deleteUser(Long id);
}

编写 SQL 映射文件

为 Mapper 接口编写 SQL 映射文件,例如 UserMapper.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="com.example.mapper.UserMapper">

    <resultMap id="userResultMap" type="com.example.model.User">
        <id property="id" column="id"/>
        <result property="username" column="username"/>
        <result property="password" column="password"/>
    </resultMap>

    <select id="selectUserById" parameterType="long" resultMap="userResultMap">
        SELECT * FROM users WHERE id = #{id}
    </select>

    <select id="selectAllUsers" resultMap="userResultMap">
        SELECT * FROM users
    </select>

    <insert id="insertUser" parameterType="com.example.model.User">
        INSERT INTO users (username, password) VALUES (#{username}, #{password})
    </insert>

    <update id="updateUser" parameterType="com.example.model.User">
        UPDATE users SET username = #{username}, password = #{password} WHERE id = #{id}
    </update>

    <delete id="deleteUser" parameterType="long">
        DELETE FROM users WHERE id = #{id}
    </delete>

</mapper>

配置 Spring(可选)

如果使用 Spring 进行整合,需要配置 Spring 的相关内容。

Spring 配置文件(applicationContext.xml)
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/jdbc
                           http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd">

    <context:component-scan base-package="com.example"/>

    <bean id="dataSource" class="com.zaxxer.hikari.HikariDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis_example?useSSL=false&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="password"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="mapperLocations" value="classpath*:com/example/mapper/*.xml"/>
    </bean>

    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.example.mapper"/>
    </bean>

    <tx:annotation-driven/>
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

测试

编写单元测试或集成测试,确保配置正确并验证功能。

package com.example;

import com.example.mapper.UserMapper;
import com.example.model.User;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class MyBatisApplicationTests {

    @Autowired
    private UserMapper userMapper;

    @Test
    public void testSelectUserById() {
        User user = userMapper.selectUserById(1L);
        System.out.println(user);
    }

    @Test
    public void testSelectAllUsers() {
        List<User> users = userMapper.selectAllUsers();
        users.forEach(System.out::println);
    }

    @Test
    public void testInsertUser() {
        User user = new User();
        user.setUsername("newUser");
        user.setPassword("password");
        userMapper.insertUser(user);
    }

    @Test
    public void testUpdateUser() {
        User user = userMapper.selectUserById(1L);
        user.setPassword("newPassword");
        userMapper.updateUser(user);
    }

    @Test
    public void testDeleteUser() {
        userMapper.deleteUser(1L);
    }
}

启动应用程序

根据项目的具体情况,配置 Spring Boot 或其他启动方式,确保应用程序可以正确运行。

总结

以上是一个完整的 MyBatis 项目开发步骤,包括环境配置、实体类和 Mapper 接口的创建、SQL 映射文件的编写、配置文件的设置以及应用程序的测试和运行。通过遵循这些步骤,可以快速高效地开发一个基于 MyBatis 的持久层解决方案。

JPA/Hibernate 核心接口对象及其作用

JPA 核心接口对象及其作用

JPA(Java Persistence API)和 Hibernate 是 Java 中的 ORM(对象关系映射)框架,它们提供了便捷的 API 用于数据库操作。JPA 定义了一组标准的接口和规范,而 Hibernate 作为 JPA 的实现之一,提供了具体的实现。以下是 JPA 和 Hibernate 中一些核心接口及其作用的详细说明。

EntityManager`

EntityManager 是 JPA 中最重要的接口,用于管理实体对象的生命周期。它提供了查询、插入、更新和删除实体的方法。

  • 创建/删除实体persist(Object entity) 用于保存实体,remove(Object entity) 用于删除实体。
  • 查找实体find(Class<T> entityClass, Object primaryKey) 根据主键查找实体,getReference(Class<T> entityClass, Object primaryKey) 返回实体的引用。
  • 查询createQuery(String qlString) 创建 JPQL 查询,createNamedQuery(String name) 创建命名查询。
@Entity
public class User {
    @Id
    private Long id;
    private String username;
    private String password;
    // Getters and setters
}

public class UserService {
    @PersistenceContext
    private EntityManager entityManager;

    public void addUser(User user) {
        entityManager.persist(user);
    }

    public User getUser(Long id) {
        return entityManager.find(User.class, id);
    }
}
EntityTransaction

EntityTransaction 接口用于管理资源级事务,适用于非容器管理的事务环境。

  • 事务控制begin() 开始事务,commit() 提交事务,rollback() 回滚事务。
EntityManager em = ...;
EntityTransaction tx = em.getTransaction();
try {
    tx.begin();
    User user = new User();
    em.persist(user);
    tx.commit();
} catch (Exception e) {
    tx.rollback();
}
Query

Query 接口用于执行静态和动态查询。

  • 设置参数setParameter(String name, Object value) 设置查询参数。
  • 执行查询getResultList() 返回结果列表,getSingleResult() 返回单个结果。
List<User> users = entityManager.createQuery("SELECT u FROM User u", User.class).getResultList();

Hibernate 核心接口及其作用

Session`

Session 是 Hibernate 中的核心接口,类似于 JPA 的 EntityManager。它用于执行 CRUD 操作和查询。

  • 生命周期管理save(Object entity) 保存实体,delete(Object entity) 删除实体。
  • 查询createQuery(String queryString) 创建 HQL 查询。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
User user = new User();
session.save(user);
tx.commit();
session.close();
SessionFactory`

SessionFactory 是一个线程安全的工厂类,用于创建 Session 对象。它是 Hibernate 中最重要的对象之一,通常在应用程序启动时创建一次。

  • 创建 SessionopenSession() 创建新的 SessiongetCurrentSession() 获取当前线程绑定的 Session
SessionFactory sessionFactory = new Configuration().configure().buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction

Transaction 接口用于处理事务,类似于 JPA 的 EntityTransaction

  • 事务控制begin() 开始事务,commit() 提交事务,rollback() 回滚事务。
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
try {
    // 业务操作
    tx.commit();
} catch (Exception e) {
    tx.rollback();
}
session.close();
Criteria

Criteria 接口用于创建类型安全的面向对象的查询。

  • 添加条件add(Criterion criterion) 添加查询条件。
  • 执行查询list() 执行查询并返回结果列表。
Criteria criteria = session.createCriteria(User.class);
criteria.add(Restrictions.eq("username", "john"));
List<User> users = criteria.list();

总结

JPA 和 Hibernate 提供了一系列核心接口,简化了 Java 应用程序与数据库的交互。JPA 的核心接口包括 EntityManagerEntityTransactionQuery,而 Hibernate 的核心接口包括 SessionSessionFactoryTransactionCriteria。这些接口提供了丰富的 API,用于管理实体的生命周期、执行查询以及控制事务。通过合理使用这些接口,可以有效地进行持久层的开发。

持久化对象的状态与生命周期

在 JPA 和 Hibernate 中,持久化对象的状态和生命周期是重要的概念。理解这些状态和它们之间的转换对于有效地管理实体对象至关重要。持久化对象的生命周期包括以下几个状态:

  1. 瞬态(Transient)状态
  2. 持久化(Persistent)状态
  3. 游离(Detached)状态
  4. 删除(Removed)状态

瞬态(Transient)状态

一个新创建的对象,未与任何持久化上下文(Persistence Context)关联,也未被持久化到数据库中。瞬态对象不受 JPA 或 Hibernate 的管理。

特点

  • 不会与数据库交互。
  • 不存在于任何持久化上下文中。

示例

User user = new User();
user.setUsername("john");
user.setPassword("password");
// 此时user对象是瞬态的

持久化(Persistent)状态

一个对象在持久化上下文中被管理,且其状态与数据库同步。持久化上下文会自动跟踪持久化对象的变化,并在事务提交时将这些变化同步到数据库。

特点

  • 与持久化上下文关联。
  • 在事务提交时自动同步到数据库。

示例

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
User user = new User();
user.setUsername("john");
user.setPassword("password");
em.persist(user); // 此时user对象是持久化的
em.getTransaction().commit();

游离(Detached)状态

一个对象曾经是持久化状态,但其持久化上下文已经关闭或分离。此时,对象的状态不再自动同步到数据库,需要手动管理。

特点

  • 不再与持久化上下文关联。
  • 仍然存在于内存中,可以重新关联到持久化上下文中。

示例

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
User user = em.find(User.class, 1L); // user对象是持久化的
em.getTransaction().commit();
em.close(); // 关闭持久化上下文
// 此时user对象是游离的

删除(Removed)状态

一个对象被标记为从数据库中删除,且处于持久化上下文中。删除操作会在事务提交时生效。

特点

  • 与持久化上下文关联。
  • 在事务提交时从数据库中删除。

示例

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();
User user = em.find(User.class, 1L); // user对象是持久化的
em.remove(user); // user对象被标记为删除
em.getTransaction().commit(); // 删除操作生效

状态转换

以下是持久化对象在生命周期中可能的状态转换:

  1. 瞬态 → 持久化

    • 调用 EntityManagerpersist() 方法。
    • 例如:em.persist(user);
  2. 持久化 → 游离

    • 关闭持久化上下文或调用 EntityManagerdetach() 方法。
    • 例如:em.close();em.detach(user);
  3. 游离 → 持久化

    • 调用 EntityManagermerge() 方法。
    • 例如:em.merge(user);
  4. 持久化 → 删除

    • 调用 EntityManagerremove() 方法。
    • 例如:em.remove(user);
  5. 瞬态 → 游离

    • 瞬态对象不能直接转换为游离状态,需先转换为持久化状态,然后再转换为游离状态。

示例综合

以下是一个综合示例,演示持久化对象的状态转换:

EntityManager em = entityManagerFactory.createEntityManager();
em.getTransaction().begin();

// 瞬态状态
User user = new User();
user.setUsername("john");
user.setPassword("password");

// 瞬态 → 持久化
em.persist(user);

// 持久化 → 游离
em.detach(user);

// 游离 → 持久化
User managedUser = em.merge(user);

// 持久化 → 删除
em.remove(managedUser);

em.getTransaction().commit();
em.close();

总结

理解 JPA 和 Hibernate 中持久化对象的状态与生命周期有助于更有效地管理实体对象。这些状态包括瞬态、持久化、游离和删除,且每个状态都有其特点和适用的场景。通过掌握这些状态及其转换,开发者可以更好地控制对象的持久化行为,提高应用程序的性能和稳定性。

JPA/Hibernate 单表映射,双向 1 对多映射注解配置

在 JPA/Hibernate 中,单表映射和双向一对多映射是非常常见的配置。下面我们将详细讲解如何使用注解来配置这两种映射关系。

单表映射

单表映射是指将一个 Java 类映射到数据库中的一张表。JPA 提供了一组注解来完成这种映射。

示例:

假设我们有一个 User 类和一个对应的数据库表 users

实体类 User

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    // Getters and setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

双向一对多映射

双向一对多映射是指一个实体类包含对另一个实体类的多个实例的引用,而这些实例也包含对第一个实体类的引用。这种映射关系通常用在父子关系或主从关系中。

示例:

假设我们有两个实体类:UserPost。一个 User 可以有多个 Post,一个 Post 也可以对应一个 User

实体类 User

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Table(name = "users")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Post> posts = new HashSet<>();

    // Getters and setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Set<Post> getPosts() {
        return posts;
    }

    public void setPosts(Set<Post> posts) {
        this.posts = posts;
    }

    public void addPost(Post post) {
        posts.add(post);
        post.setUser(this);
    }

    public void removePost(Post post) {
        posts.remove(post);
        post.setUser(null);
    }
}

实体类 Post

import javax.persistence.*;

@Entity
@Table(name = "posts")
public class Post {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;

    private String content;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    // Getters and setters

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public User getUser() {
        return user;
    }

    public void setUser(User user) {
        this.user = user;
    }
}

详细解释

  1. @Entity@Table
  • @Entity 注解标记该类为一个实体类,JPA 将会将其映射到数据库表。
  • @Table 注解指定了映射的数据库表名。
  1. 主键和自动生成策略
  • @Id 注解标记该字段为实体的主键。
  • @GeneratedValue 注解指定了主键的生成策略,这里使用 GenerationType.IDENTITY,表示主键由数据库自动生成(通常用于自增字段)。
  1. 双向一对多映射
  • @OneToMany 注解用于定义一对多关系。mappedBy 属性指定了 Post 实体中维护关系的字段,即 Post 中的 user 字段。
  • cascade = CascadeType.ALL 指定了级联操作,即对 User 的增删改操作会传递到关联的 Post 对象。
  • orphanRemoval = true 表示删除 User 时,自动删除其关联的 Post 对象。
  • @ManyToOne 注解用于定义多对一关系。fetch = FetchType.LAZY 指定了懒加载策略,即在访问 Postuser 属性时才会加载对应的 User 对象。
  • @JoinColumn 注解定义了关联列(外键)。

使用示例

下面是一个完整的示例,演示如何创建、查询和删除 UserPost 对象。

创建用户和帖子

EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

User user = new User();
user.setUsername("john");
user.setPassword("password");

Post post1 = new Post();
post1.setTitle("First Post");
post1.setContent("Content of the first post");

Post post2 = new Post();
post2.setTitle("Second Post");
post2.setContent("Content of the second post");

user.addPost(post1);
user.addPost(post2);

em.persist(user);
em.getTransaction().commit();
em.close();
emf.close();

查询用户及其帖子

EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
EntityManager em = emf.createEntityManager();

User user = em.find(User.class, 1L);
System.out.println("User: " + user.getUsername());
for (Post post : user.getPosts()) {
    System.out.println("Post: " + post.getTitle());
}

em.close();
emf.close();

删除用户及其帖子

EntityManagerFactory emf = Persistence.createEntityManagerFactory("example-unit");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();

User user = em.find(User.class, 1L);
em.remove(user);

em.getTransaction().commit();
em.close();
emf.close();

通过以上配置和示例,可以看到如何使用 JPA 和 Hibernate 注解配置单表映射和双向一对多映射,并且通过实体类之间的关系操作实现增删改查操作。

MyBatis 与 JPA/Hibernate 实现 ORM 的比较

MyBatis 和 JPA/Hibernate 都是用于 Java 持久化层的框架,但它们在实现 ORM(对象关系映射)的方式和应用场景上有显著的不同。下面将从多个方面对这两个框架进行比较。

实现方式

MyBatis

  • SQL 映射:MyBatis 通过 SQL 映射文件或注解将 Java 方法与 SQL 语句关联。开发者需要手动编写 SQL 语句,因此可以精确控制 SQL 的执行。
  • 半自动化:MyBatis 提供了一些自动化的功能,如参数映射和结果映射,但整体上更接近手动编写 SQL。

JPA/Hibernate

  • 全自动化 ORM:JPA/Hibernate 通过注解或 XML 配置将 Java 对象与数据库表进行映射,并自动生成 SQL 语句。开发者不需要编写大部分 SQL 语句,框架会自动处理对象的持久化、查询等操作。
  • 实体管理:提供了丰富的 API 用于管理实体对象的生命周期,支持复杂的关系映射和懒加载等高级特性。

开发效率

MyBatis

  • 高控制性:开发者可以精确控制 SQL 语句,适合需要优化 SQL 性能的场景。
  • 灵活性高:可以针对复杂的查询和存储过程进行细粒度的优化。
  • 开发速度较慢:需要手动编写和维护 SQL 语句,复杂的查询可能会增加开发时间。

JPA/Hibernate

  • 高自动化:通过注解或配置文件定义映射关系,框架自动生成 SQL,简化了开发工作。
  • 快速开发:自动化特性使得开发速度较快,尤其是在处理常见的 CRUD 操作时。
  • 调优难度大:由于 SQL 生成是自动的,对生成的 SQL 进行调优可能比较困难。

性能

MyBatis

  • 性能可控:开发者可以优化每一条 SQL 语句,确保性能最佳。
  • 适合复杂查询:对于复杂查询和批量操作,MyBatis 可以更高效地执行。

JPA/Hibernate

  • 性能依赖框架优化:由于 SQL 语句由框架自动生成,性能优化更多依赖于框架本身的实现。
  • 延迟加载:支持延迟加载,可以优化性能,但需要注意避免 N+1 查询问题。
  • 缓存机制:内置一级和二级缓存,提高查询性能。

学习曲线

MyBatis

  • 学习曲线较低:需要了解基本的 SQL 和 MyBatis 的映射配置,容易上手。
  • 对 SQL 熟悉度要求高:开发者需要对 SQL 有较高的掌握。

JPA/Hibernate

  • 学习曲线较陡:需要理解 JPA 规范、实体映射、JPQL(Java Persistence Query Language)以及复杂的配置和优化技巧。
  • 复杂关系映射学习成本高:处理复杂的对象关系映射和继承关系时,需要深入理解框架的工作原理。

配置和使用

MyBatis

  • 配置简单:通过 XML 或注解配置 SQL 映射,灵活性高。
  • 依赖 SQL:每个数据库操作需要手动编写 SQL 语句。

JPA/Hibernate

  • 配置复杂:通过注解或 XML 配置实体和关系映射,自动化程度高。
  • 依赖 JPA 规范:使用标准的 JPA API 进行操作,减少对 SQL 的依赖。

事务管理

MyBatis

  • 手动事务控制:需要手动管理事务,通过 Spring 等框架可以简化事务管理。
  • 细粒度控制:可以精细地控制事务的提交和回滚。

JPA/Hibernate

  • 自动事务管理:框架自动管理事务,支持声明式事务,简化开发工作。
  • 事务一致性:通过持久化上下文保证事务的一致性。

适用场景

MyBatis

  • 复杂查询和批处理:适合需要编写复杂 SQL 和进行批量操作的应用场景。
  • 性能要求高:适用于对性能有严格要求,需要对 SQL 进行优化的场景。
  • 控制要求高:适合需要细粒度控制 SQL 执行的场景。

JPA/Hibernate

  • 快速开发:适合快速开发和迭代的项目,特别是常见的 CRUD 操作。
  • 复杂关系映射:适用于需要处理复杂对象关系和继承结构的场景。
  • 标准化需求:适合需要遵循 JPA 标准的企业级应用。

总结

  • MyBatis:灵活、可控、适合复杂查询和批处理,对 SQL 优化和性能调优有更高要求。
  • JPA/Hibernate:自动化程度高、开发效率高、适合快速开发和复杂关系映射,但调优难度较大。

选择使用 MyBatis 还是 JPA/Hibernate,取决于具体的项目需求、团队的技术背景和性能优化的要求。在实际开发中,也有许多项目会将两者结合使用,取长补短,发挥各自的优势。

SpringMVC

Web MVC 的演进

Web MVC(Model-View-Controller)是 Web 应用程序开发中的一种设计模式,通过将应用程序的不同功能分离开来,帮助开发人员更好地管理代码。以下是 Web MVC 的演进过程,从早期的简单实现到现代的复杂框架。

传统的 Servlet 和 JSP

特点

  • Servlet:早期的 Java Web 应用使用 Servlet 来处理 HTTP 请求和响应。Servlet 是 Java 类,继承自 HttpServlet,覆盖 doGetdoPost 方法。
  • JSP(JavaServer Pages):用于生成动态 HTML 内容。JSP 页面嵌入了 Java 代码,但这种做法会导致代码混乱。

缺点

  • 耦合度高:业务逻辑、控制逻辑和视图混杂在一起,代码难以维护。
  • 可测试性差:由于逻辑和视图紧密耦合,单元测试困难。

Struts 1.x

特点

  • 基于 Action:引入了 Action 类,负责处理请求和生成响应。
  • 配置文件:使用 struts-config.xml 来配置请求路径与 Action 类的映射。

优点

  • 清晰的分层:分离了控制逻辑和视图,提高了代码的可维护性。
  • 扩展性:通过插件和拦截器扩展功能。

缺点

  • 配置复杂:XML 配置文件过于复杂,增加了学习曲线。
  • Action 类耦合:Action 类与 Servlet API 紧密耦合,不利于单元测试。

Spring MVC

特点

  • 基于注解:使用注解(如 @Controller@RequestMapping)来简化配置。
  • IoC(控制反转):通过 Spring 容器管理 Bean 的生命周期。
  • AOP(面向切面编程):支持 AOP 来处理横切关注点(如事务、日志)。

优点

  • 简化配置:基于注解减少了 XML 配置文件的复杂度。
  • 松散耦合:通过依赖注入实现松散耦合,提高代码的可测试性。
  • 强大的生态系统:集成了 Spring 生态系统中的其他模块,如 Spring Security、Spring Data 等。

缺点

  • 学习曲线:功能强大,但需要一定的学习时间来掌握。

JSF(JavaServer Faces)

特点

  • 组件化:基于组件的框架,视图由可重用的 UI 组件构建。
  • 双向数据绑定:简化了视图与模型之间的数据同步。

优点

  • 组件复用:支持 UI 组件的重用,提高开发效率。
  • 状态管理:内置状态管理,适合开发复杂的用户界面。

缺点

  • 性能问题:内置状态管理可能导致性能问题,特别是在大规模应用中。
  • 复杂性:配置和使用较为复杂,开发和调试相对困难。

Modern Web Frameworks(如 Spring Boot、Spring WebFlux)

特点

  • 微服务架构:支持微服务架构,简化了微服务的开发和部署。
  • 响应式编程:Spring WebFlux 支持响应式编程模型,适合高并发和低延迟的应用场景。

优点

  • 快速开发:Spring Boot 通过自动配置和约定优于配置,加快开发速度。
  • 响应式编程:通过响应式流处理高并发请求,提高系统的吞吐量和性能。
  • 云原生支持:集成了 Spring Cloud,支持微服务架构和云原生应用的开发。

缺点

  • 复杂性:虽然 Spring Boot 简化了配置,但对于初学者来说,理解底层机制和高级特性仍然需要一定的学习时间。

总结

Web MVC 框架从早期的 Servlet 和 JSP 到现代的 Spring Boot 和 Spring WebFlux,经历了显著的演进过程。每一代框架都在解决前一代框架中的不足,提升开发效率、可维护性和性能。现代的 Web MVC 框架,如 Spring Boot,通过自动配置和强大的生态系统,为开发者提供了更为便捷和强大的开发工具,支持快速开发和部署高性能、可扩展的 Web 应用。

演进路线图

  1. Servlet 和 JSP:直接处理请求和生成响应,逻辑和视图混杂。
  2. Struts 1.x:引入 Action 类和 XML 配置,初步实现 MVC 分层。
  3. Spring MVC:基于注解和 IoC 容器,简化配置,提高可维护性。
  4. JSF:组件化框架,提供双向数据绑定和状态管理。
  5. Spring Boot 和 Spring WebFlux:支持微服务架构和响应式编程,适合现代高并发应用。

每一步演进都在一定程度上解决了前一代技术的不足,并引入了新的特性和优化,使得 Web 开发变得更加高效和便捷。

Spring mvc 的请求处理及响应的处理全流程

请求处理流程

  1. 客户端发送请求
    • 客户端(通常是浏览器)发送一个 HTTP 请求到服务器。
  2. DispatcherServlet 接收请求
    • 请求到达服务器,由 Spring 配置的前端控制器 DispatcherServlet 接收。
    • DispatcherServlet 是整个 Spring MVC 的核心,负责协调其他组件的工作。
  3. 处理器映射(Handler Mapping)
    • DispatcherServlet 将请求传递给一个或多个 HandlerMapping,以确定哪个处理器(Controller)来处理请求。
    • HandlerMapping 是一个策略接口,其实现类如 RequestMappingHandlerMapping 用于根据请求 URL、HTTP 方法等查找对应的处理器。
  4. 处理器适配器(Handler Adapter)
    • DispatcherServlet 根据找到的处理器选择合适的 HandlerAdapter 来执行处理器的方法。
    • HandlerAdapter 负责调用处理器的方法,并返回一个 ModelAndView 对象。
  5. 处理器(Controller)
    • HandlerAdapter 调用具体的处理器(Controller)方法。
    • Controller 处理业务逻辑,通常会调用服务层和数据访问层,处理完毕后返回一个 ModelAndView 对象。

响应处理流程

  1. 视图解析器(View Resolver)
    • DispatcherServlet 使用一个或多个 ViewResolver 解析 ModelAndView 中的视图名称,并选择合适的视图(View)对象。
    • 常见的视图解析器包括 InternalResourceViewResolverThymeleafViewResolver 等。
  2. 视图渲染
    • View 对象负责将模型数据渲染成最终的响应。
    • 渲染后的结果通常是 HTML、JSON、XML 等格式,返回给客户端。
  3. 响应返回客户端
    • 渲染后的响应通过 DispatcherServlet 返回给客户端,完成整个请求处理流程。

完整调用链:

SpringMVC 控制器的分类与作用

前置控制器(DispatcherServlet)

作用

  • 前置控制器(DispatcherServlet)是 Spring MVC 框架的核心,它接收所有的 HTTP 请求并将其分派到适当的处理器(Controller)进行处理。
  • DispatcherServlet 作为前端控制器,协调其他组件如处理器映射、处理器适配器、视图解析器等的工作。

工作流程

  1. 接收客户端请求。
  2. 通过处理器映射(HandlerMapping)找到相应的处理器。
  3. 通过处理器适配器(HandlerAdapter)调用处理器方法。
  4. 将处理器方法的返回结果交给视图解析器(ViewResolver)进行视图解析。
  5. 将视图渲染结果返回给客户端。

示例配置

web.xml 中配置 DispatcherServlet:

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

核心控制器(HandlerMapping、HandlerAdapter)

作用

  • 核心控制器包括 HandlerMapping 和 HandlerAdapter,它们是 DispatcherServlet 的重要组成部分,负责请求的处理和分派。

HandlerMapping

  • 作用:负责将 HTTP 请求映射到处理器上,即找到合适的 Controller 来处理请求。
  • 实现类:常见的实现包括 RequestMappingHandlerMapping,它根据请求的 URL 和 HTTP 方法来查找处理器。

HandlerAdapter

  • 作用:负责执行找到的处理器(Controller)的方法。
  • 实现类:常见的实现包括 RequestMappingHandlerAdapter,它支持基于注解的控制器方法。

示例配置

在 Spring 配置文件中定义 HandlerMapping 和 HandlerAdapter:

<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

用户控制器(User Controller)

作用

  • 用户控制器是由开发者编写的具体处理业务逻辑的控制器类,负责处理用户请求、调用业务服务并返回视图或数据。
  • 使用 @Controller@RestController 注解标记。

示例

使用 @Controller

@Controller
public class UserController {

    @RequestMapping("/user/profile")
    public String userProfile(Model model) {
        // 处理业务逻辑
        model.addAttribute("user", userService.getUserProfile());
        return "profile"; // 返回视图名称,将会被视图解析器解析
    }
}

使用 @RestController

@RestController
@RequestMapping("/api/users")
public class UserRestController {

    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        // 处理业务逻辑
        return userService.getUserById(id); // 返回用户对象,自动转换为JSON
    }
}

总结

  • 前置控制器(DispatcherServlet):整个 Spring MVC 框架的核心,接收所有请求并分派给适当的处理器。
  • 核心控制器(HandlerMapping、HandlerAdapter):负责将请求映射到具体的处理器,并执行处理器的方法。
  • 用户控制器(User Controller):由开发者编写,处理具体的业务逻辑,返回视图或数据。

通过这三种类型的控制器,Spring MVC 实现了请求处理流程的解耦和模块化,方便开发者编写、维护和扩展 Web 应用程序。

SpringMVC 的 URL 映射

Spring MVC 中的 URL 映射是指将 HTTP 请求的 URL 路径映射到具体的处理器方法(控制器方法)上。Spring MVC 提供了多种方式来配置和管理 URL 映射,主要是通过注解来实现。这些注解可以在类级别和方法级别上使用。

@RequestMapping

@RequestMapping 是 Spring MVC 中最基本和最常用的 URL 映射注解。它可以用在类级别和方法级别来指定 URL 路径、HTTP 方法、请求参数、请求头等。

类级别映射
@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/profile")
    public String userProfile() {
        // 处理逻辑
        return "profile"; // 返回视图名称
    }

    @RequestMapping("/settings")
    public String userSettings() {
        // 处理逻辑
        return "settings"; // 返回视图名称
    }
}

在这个例子中,UserController 类的请求路径前缀是 /user。具体方法的请求路径分别是 /profile/settings,所以完整的 URL 路径分别是 /user/profile/user/settings

方法级别映射
@Controller
@RequestMapping("/products")
public class ProductController {

    @RequestMapping(value = "/list", method = RequestMethod.GET)
    public String listProducts() {
        // 处理逻辑
        return "productList"; // 返回视图名称
    }

    @RequestMapping(value = "/add", method = RequestMethod.POST)
    public String addProduct(@ModelAttribute Product product) {
        // 处理逻辑
        return "productDetail"; // 返回视图名称
    }
}

在这个例子中,listProducts 方法处理 GET 请求的 /products/list 路径,而 addProduct 方法处理 POST 请求的 /products/add 路径。

短路由注解:@GetMapping, @PostMapping, @PutMapping, @DeleteMapping, @PatchMapping

为了简化 @RequestMapping 的配置,Spring MVC 提供了更具体的注解来映射特定的 HTTP 方法。

@GetMapping
@Controller
@RequestMapping("/orders")
public class OrderController {

    @GetMapping("/all")
    public String getAllOrders() {
        // 处理逻辑
        return "orderList"; // 返回视图名称
    }
}
@PostMapping
@Controller
@RequestMapping("/orders")
public class OrderController {

    @PostMapping("/create")
    public String createOrder(@ModelAttribute Order order) {
        // 处理逻辑
        return "orderDetail"; // 返回视图名称
    }
}
@PutMapping
@Controller
@RequestMapping("/orders")
public class OrderController {

    @PutMapping("/update/{id}")
    public String updateOrder(@PathVariable Long id, @ModelAttribute Order order) {
        // 处理逻辑
        return "orderDetail"; // 返回视图名称
    }
}
@DeleteMapping
@Controller
@RequestMapping("/orders")
public class OrderController {

    @DeleteMapping("/delete/{id}")
    public String deleteOrder(@PathVariable Long id) {
        // 处理逻辑
        return "orderList"; // 返回视图名称
    }
}

路径变量和请求参数

路径变量(@PathVariable
@Controller
@RequestMapping("/users")
public class UserController {

    @GetMapping("/{userId}")
    public String getUser(@PathVariable String userId, Model model) {
        // 处理逻辑
        model.addAttribute("user", userService.getUserById(userId));
        return "userDetail"; // 返回视图名称
    }
}

在这个例子中,@PathVariable 注解用于将 URL 路径中的变量 userId 映射到方法参数上。

请求参数(@RequestParam
@Controller
@RequestMapping("/search")
public class SearchController {

    @GetMapping("/results")
    public String search(@RequestParam String query, Model model) {
        // 处理逻辑
        model.addAttribute("results", searchService.search(query));
        return "searchResults"; // 返回视图名称
    }
}

在这个例子中,@RequestParam 注解用于将请求参数 query 映射到方法参数上。

组合注解

Spring 还允许组合多个条件来进行更复杂的映射。

@RestController
@RequestMapping("/api")
public class ApiController {

    @RequestMapping(value = "/data", method = RequestMethod.GET, params = "type=full", headers = "Accept=application/json")
    public Data getFullData() {
        // 处理逻辑
        return new Data(); // 返回数据
    }
}

在这个例子中,getFullData 方法只有在请求满足以下所有条件时才会被调用:

  • 请求路径为 /api/data
  • HTTP 方法为 GET
  • 请求参数包含 type=full
  • 请求头包含 Accept=application/json

映射优先级

Spring MVC 允许在同一个控制器中定义多个 URL 映射方法。在这种情况下,Spring MVC 会根据具体的映射规则确定优先级,优先匹配更具体的映射。

@Controller
@RequestMapping("/example")
public class ExampleController {

    @GetMapping("/test")
    public String test1() {
        // 处理逻辑
        return "view1";
    }

    @GetMapping("/test/{id}")
    public String test2(@PathVariable String id) {
        // 处理逻辑
        return "view2";
    }
}

在这个例子中,如果请求路径是 /example/testtest1 方法会被调用。如果请求路径是 /example/test/123test2 方法会被调用,因为它更具体地匹配了请求路径。

总结

Spring MVC 提供了灵活多样的方式来配置 URL 映射,通过注解如 @RequestMapping@GetMapping@PostMapping 等,以及结合路径变量和请求参数,可以实现精确且高效的请求处理逻辑。这种配置方式不仅简化了开发工作,还提高了代码的可读性和可维护性。

SpringMVC 的参数绑定/命令对象属性绑定

在 Spring MVC 中,参数绑定(Parameter Binding)是指将 HTTP 请求中的参数(包括请求参数、路径变量、表单数据等)自动绑定到控制器方法的参数上。命令对象属性绑定(Command Object Property Binding)是指将请求中的参数自动映射到 Java 对象的属性上。这些特性使得处理复杂的表单提交和数据传输变得非常简单和直观。

参数绑定

1. 简单类型参数绑定

Spring MVC 可以直接将请求参数绑定到控制器方法的参数上。常见的注解有 @RequestParam@PathVariable

@RequestParam 示例

@Controller
@RequestMapping("/user")
public class UserController {

    @GetMapping("/profile")
    public String userProfile(@RequestParam String username, Model model) {
        // 使用请求参数username
        model.addAttribute("username", username);
        return "profile"; // 返回视图名称
    }
}

在这个示例中,@RequestParam 用于将请求参数 username 绑定到方法参数 username 上。

@PathVariable 示例

@Controller
@RequestMapping("/user")
public class UserController {

    @GetMapping("/profile/{id}")
    public String userProfile(@PathVariable Long id, Model model) {
        // 使用路径变量id
        User user = userService.findUserById(id);
        model.addAttribute("user", user);
        return "profile"; // 返回视图名称
    }
}

在这个示例中,@PathVariable 用于将 URL 路径中的变量 id 绑定到方法参数 id 上。

命令对象属性绑定

命令对象属性绑定是指将 HTTP 请求中的参数自动映射到 Java 对象的属性上,通常用于处理复杂表单数据。

使用命令对象

命令对象示例

public class User {
    private String username;
    private String password;
    // getters and setters
}

控制器方法

@Controller
@RequestMapping("/user")
public class UserController {

    @PostMapping("/register")
    public String registerUser(@ModelAttribute User user, Model model) {
        // 使用命令对象user
        userService.saveUser(user);
        model.addAttribute("user", user);
        return "registrationSuccess"; // 返回视图名称
    }
}

在这个示例中,@ModelAttribute 用于将请求参数绑定到命令对象 user 上。Spring 会自动将请求参数 usernamepassword 映射到 User 对象的相应属性上。

嵌套对象属性绑定

如果命令对象包含嵌套对象,Spring MVC 也能自动完成嵌套对象的属性绑定。

嵌套对象示例

public class Address {
    private String street;
    private String city;
    // getters and setters
}

public class User {
    private String username;
    private String password;
    private Address address;
    // getters and setters
}

控制器方法

@Controller
@RequestMapping("/user")
public class UserController {

    @PostMapping("/register")
    public String registerUser(@ModelAttribute User user, Model model) {
        // 使用命令对象user及其嵌套的address对象
        userService.saveUser(user);
        model.addAttribute("user", user);
        return "registrationSuccess"; // 返回视图名称
    }
}

在这个示例中,请求参数 usernamepasswordaddress.streetaddress.city 会被自动绑定到 User 对象及其嵌套的 Address 对象的属性上。

自定义数据绑定

对于复杂类型或者需要自定义绑定逻辑的场景,可以使用 @InitBinder 注解来定义一个初始化绑定器的方法。

自定义数据绑定示例

@Controller
@RequestMapping("/user")
public class UserController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        // 自定义的绑定器,例如日期格式转换
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    @PostMapping("/register")
    public String registerUser(@ModelAttribute User user, Model model) {
        // 使用命令对象user
        userService.saveUser(user);
        model.addAttribute("user", user);
        return "registrationSuccess"; // 返回视图名称
    }
}

在这个示例中,@InitBinder 方法用于注册一个自定义的属性编辑器 CustomDateEditor,将请求参数中的日期字符串转换为 Date 对象。

总结

  • 简单类型参数绑定:使用 @RequestParam@PathVariable 将请求参数和路径变量绑定到方法参数上。
  • 命令对象属性绑定:使用 @ModelAttribute 将请求参数自动绑定到 Java 对象的属性上,适用于复杂表单数据。
  • 嵌套对象属性绑定:Spring MVC 支持命令对象中嵌套对象的属性绑定。
  • 自定义数据绑定:使用 @InitBinder 定义自定义的数据绑定逻辑,处理复杂类型转换和自定义绑定需求。

这些参数绑定和命令对象属性绑定的机制,使得 Spring MVC 在处理 HTTP 请求和表单提交时变得非常高效和灵活。

SpringMVC 参数数据校验

引入依赖

在使用 Spring 6 时,需要使用 Jakarta EE 的校验 API。对于 Maven 项目,确保在 pom.xml 中添加以下依赖:

<dependency>
    <groupId>jakarta.validation</groupId>
    <artifactId>jakarta.validation-api</artifactId>
    <version>3.0.2</version>
</dependency>
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>7.0.4.Final</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

创建实体类和校验注解

使用 Jakarta EE 的校验注解在实体类中定义数据校验规则。例如:

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.Size;

public class User {

    @NotEmpty(message = "Username is required")
    @Size(min = 2, max = 30, message = "Username must be between 2 and 30 characters")
    private String username;

    @NotEmpty(message = "Email is required")
    @Email(message = "Email should be valid")
    private String email;

    @NotEmpty(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters long")
    private String password;

    // Getters and setters
}

在控制器中使用校验

在控制器中,使用 @Valid 注解来触发校验,并通过 BindingResult 对象来处理校验结果。

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;

import jakarta.validation.Valid;

@Controller
public class UserController {

    @GetMapping("/register")
    public String showRegistrationForm(Model model) {
        model.addAttribute("user", new User());
        return "register";
    }

    @PostMapping("/register")
    public String registerUser(@Valid @ModelAttribute("user") User user, BindingResult bindingResult, Model model) {
        if (bindingResult.hasErrors()) {
            return "register";
        }
        // 处理注册逻辑
        model.addAttribute("message", "User registered successfully");
        return "registrationSuccess";
    }
}

处理校验错误

在视图层中,处理和显示校验错误信息。以下是使用 Thymeleaf 作为视图模板引擎的示例:

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Registration Form</title>
</head>
<body>
    <h1>Register</h1>
    <form th:action="@{/register}" th:object="${user}" method="post">
        <div>
            <label for="username">Username:</label>
            <input type="text" id="username" th:field="*{username}" />
            <span th:if="${#fields.hasErrors('username')}" th:errors="*{username}">Username Error</span>
        </div>
        <div>
            <label for="email">Email:</label>
            <input type="email" id="email" th:field="*{email}" />
            <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}">Email Error</span>
        </div>
        <div>
            <label for="password">Password:</label>
            <input type="password" id="password" th:field="*{password}" />
            <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}">Password Error</span>
        </div>
        <div>
            <button type="submit">Register</button>
        </div>
    </form>
</body>
</html>

总结

  1. 引入 Jakarta EE 校验 API:使用 jakarta.validation-apihibernate-validator
  2. 定义实体类和校验规则:在实体类中使用 Jakarta EE 的校验注解。
  3. 在控制器中使用校验:在控制器方法参数中使用 @Valid 注解,并处理 BindingResult
  4. 处理和显示校验错误:在视图模板中使用模板引擎显示校验错误信息。

SpringMVC 与 servlet api

Spring MVC 与 Servlet API 密切相关,因为 Spring MVC 是基于 Servlet API 构建的。它扩展了 Servlet 的功能,使开发 Web 应用程序更加方便和高效。了解 Spring MVC 与 Servlet API 的关系和区别,有助于更好地理解 Spring MVC 的工作原理和优势。

Servlet API 简介

Servlet API 是 Java EE(现为 Jakarta EE)的一部分,用于构建基于 HTTP 协议的 Web 应用程序。它定义了一组接口和类,处理 HTTP 请求和响应。核心组件包括:

  • Servlet:处理 HTTP 请求的 Java 类,继承 javax.servlet.http.HttpServlet
  • HttpServletRequest:封装 HTTP 请求信息的接口。
  • HttpServletResponse:封装 HTTP 响应信息的接口。
  • ServletContext:Servlet 容器的上下文信息。
  • ServletConfig:Servlet 的配置信息。

Spring MVC 与 Servlet API 的关系

Spring MVC 是一个基于 Servlet API 的 Web 框架。它使用 Servlet API 来处理 HTTP 请求,但通过以下方式扩展和简化了开发:

  1. DispatcherServlet:Spring MVC 的核心是前置控制器 DispatcherServlet,它继承自 HttpServlet,并且是 Spring MVC 的中央调度器,负责将请求分发到合适的处理器。

    public class DispatcherServlet extends FrameworkServlet {
        // 核心处理逻辑
    }
    
  2. Handler Mapping:负责将 HTTP 请求映射到处理器(控制器)方法上,常见的实现包括 RequestMappingHandlerMapping

  3. Handler Adapter:负责执行处理器方法,常见的实现包括 RequestMappingHandlerAdapter

  4. View Resolver:负责将处理器方法返回的视图名称解析为实际的视图对象,常见的实现包括 InternalResourceViewResolver

Spring MVC 与 Servlet API 的区别

  1. 简化开发

    • Servlet:开发者需要手动处理请求参数、调用业务逻辑、生成响应。
    • Spring MVC:使用注解和自动绑定,简化了请求处理流程。
  2. 注解驱动

    • Servlet:配置和映射通过 web.xml 文件进行,较为繁琐。
    • Spring MVC:使用注解(如 @Controller@RequestMapping)进行配置,更加简洁明了。
  3. 面向切面编程(AOP)

    • Servlet:缺乏内置的 AOP 支持。
    • Spring MVC:可以轻松集成 Spring AOP,实现横切关注点(如事务管理、日志记录)。
  4. 易于测试

    • Servlet:单元测试较为复杂,需要模拟 HTTP 请求和响应。
    • Spring MVC:提供了测试支持,可以使用 Mock 对象和 Spring Test 框架进行单元测试。

Spring MVC 工作流程

以下是 Spring MVC 处理 HTTP 请求的典型流程:

  1. 客户端发送请求:客户端发送 HTTP 请求到服务器。
  2. DispatcherServlet 接收请求DispatcherServlet 作为前置控制器接收所有的请求。
  3. Handler Mapping 映射请求DispatcherServlet 使用 Handler Mapping 查找匹配的处理器(控制器)方法。
  4. Handler Adapter 执行处理器方法:通过 Handler Adapter 调用处理器方法,处理请求并生成 Model 数据。
  5. View Resolver 解析视图:处理器方法返回视图名称,DispatcherServlet 使用 View Resolver 解析视图。
  6. 视图渲染:将 Model 数据传递给视图对象,视图渲染生成最终的 HTML 响应。
  7. 响应客户端:将生成的 HTML 响应返回给客户端。

代码示例

Servlet 示例

@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        response.setContentType("text/html");
        PrintWriter out = response.getWriter();
        out.println("<h1>Hello, World!</h1>");
    }
}

Spring MVC 控制器示例

@Controller
public class HelloController {
    
    @GetMapping("/hello")
    public String sayHello(Model model) {
        model.addAttribute("message", "Hello, World!");
        return "hello"; // 返回视图名称 "hello"
    }
}

视图文件(hello.jsp)

<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<html>
<body>
    <h1>${message}</h1>
</body>
</html>

总结

  • Servlet API 是 Java EE 的一部分,处理 HTTP 请求和响应,提供基本的 Web 开发功能。
  • Spring MVC 基于 Servlet API 构建,扩展并简化了 Web 开发,通过注解、自动绑定、AOP 支持和强大的测试能力,使开发更加高效。
  • DispatcherServlet 是 Spring MVC 的核心,通过 Handler Mapping、Handler Adapter 和 View Resolver 协调请求处理流程。
  • Spring MVC 通过简化配置和开发流程,极大地提高了 Web 应用程序的开发效率和可维护性。

SpringMVC 的视图解析

在 Spring MVC 中,视图解析(View Resolution)是指将控制器返回的视图名称解析为实际的视图对象,并将模型数据渲染到视图中以生成最终的响应。Spring MVC 提供了灵活且可扩展的视图解析机制,通过视图解析器(ViewResolver)来完成视图的解析。

视图解析的工作流程

  1. 控制器处理请求:控制器处理请求并返回一个视图名称和模型数据。
  2. DispatcherServlet 调度视图解析器DispatcherServlet 收到控制器返回的视图名称后,委托视图解析器将视图名称解析为实际的视图对象。
  3. 视图渲染:视图对象使用模型数据进行渲染,生成最终的 HTML 响应。
  4. 响应客户端DispatcherServlet 将渲染后的视图内容作为 HTTP 响应返回给客户端。

视图解析器的配置

Spring MVC 提供了多种视图解析器,例如 InternalResourceViewResolverThymeleafViewResolverBeanNameViewResolver 等。常用的配置方式如下:

InternalResourceViewResolver`

InternalResourceViewResolver 用于解析 JSP 视图。可以通过 Java 配置或 XML 配置来设置视图解析器。

Java 配置

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }
}

XML 配置

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>
ThymeleafViewResolver`

ThymeleafViewResolver 用于解析 Thymeleaf 视图。需要引入 Thymeleaf 相关依赖,并进行相应的配置。

Maven 依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

Java 配置

Spring Boot 自动配置了 Thymeleaf,如果需要自定义配置,可以使用以下代码:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver;
import org.thymeleaf.templateresolver.ITemplateResolver;

@Configuration
public class ThymeleafConfig {

    @Bean
    public ITemplateResolver templateResolver() {
        ClassLoaderTemplateResolver templateResolver = new ClassLoaderTemplateResolver();
        templateResolver.setPrefix("templates/");
        templateResolver.setSuffix(".html");
        templateResolver.setTemplateMode("HTML5");
        templateResolver.setCharacterEncoding("UTF-8");
        return templateResolver;
    }

    @Bean
    public SpringTemplateEngine templateEngine() {
        SpringTemplateEngine templateEngine = new SpringTemplateEngine();
        templateEngine.setTemplateResolver(templateResolver());
        return templateEngine;
    }

    @Bean
    public ThymeleafViewResolver viewResolver() {
        ThymeleafViewResolver viewResolver = new ThymeleafViewResolver();
        viewResolver.setTemplateEngine(templateEngine());
        viewResolver.setCharacterEncoding("UTF-8");
        return viewResolver;
    }
}

控制器返回视图

控制器方法可以返回视图名称,Spring MVC 会根据配置的视图解析器将其解析为实际的视图对象。

控制器示例

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String sayHello(Model model) {
        model.addAttribute("message", "Hello, World!");
        return "hello"; // 返回视图名称 "hello"
    }
}

视图文件示例

JSP 视图文件(hello.jsp)

<%@ taglib uri="http://www.springframework.org/tags" prefix="spring" %>
<html>
<body>
    <h1>${message}</h1>
</body>
</html>

Thymeleaf 视图文件(hello.html)

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Hello Page</title>
</head>
<body>
    <h1 th:text="${message}">Hello, World!</h1>
</body>
</html>

总结

  • 视图解析器:用于将视图名称解析为实际的视图对象。常用的视图解析器有 InternalResourceViewResolverThymeleafViewResolver
  • 配置方式:可以通过 Java 配置或 XML 配置视图解析器。
  • 控制器返回视图:控制器方法返回视图名称,Spring MVC 根据配置的视图解析器进行解析。
  • 视图渲染:视图对象使用模型数据进行渲染,生成最终的 HTML 响应。

通过视图解析器,Spring MVC 将控制器返回的视图名称与实际的视图文件关联起来,使得视图渲染过程变得透明和可配置,从而提高了开发效率和灵活性。

SpringMVC 的 json 数据支持

在现代 Web 应用程序中,JSON(JavaScript Object Notation)已经成为一种常见的数据交换格式。Spring MVC 提供了强大的支持来处理 JSON 数据,包括将控制器的返回值转换为 JSON 格式,以及将请求中的 JSON 数据绑定到控制器方法的参数上。

引入依赖

        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.17.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-core</artifactId>
            <version>2.17.1</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.17.1</version>
        </dependency>

配置 Spring MVC

在配置文件中启用注解支持
<beans>
	<!--    开启springMVC对注解的支持-->
    <mvc:annotation-driven/>
</beans>
配置消息转换器(可选)

Spring MVC 默认会自动配置 MappingJackson2HttpMessageConverter,它负责将 Java 对象转换为 JSON 格式,或将 JSON 数据转换为 Java 对象。通常,你不需要手动配置它,但如果你需要自定义配置,可以这样做:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import java.util.List;

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        converters.add(new MappingJackson2HttpMessageConverter());
    }
}
控制器返回 JSON 数据

使用 @ResponseBody@RestController 注解将控制器的方法返回值转换为 JSON 格式。

使用 @ResponseBody

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.Map;

@Controller
@RequestMapping("/api")
public class UserController {

    @GetMapping("/user")
    @ResponseBody
    public Map<String, Object> getUser() {
        Map<String, Object> response = new HashMap<>();
        response.put("id", 1);
        response.put("name", "John Doe");
        return response; // 将返回的Map对象转换为JSON格式
    }
}

使用 @RestController

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class UserController {

    @GetMapping("/user")
    public User getUser() {
        return new User(1, "John Doe"); // 将返回的User对象转换为JSON格式
    }
}
控制器接收 Json 数据

使用 @RequestBody 注解将请求中的 JSON 数据绑定到控制器方法的参数上。

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api")
public class UserController {

    @PostMapping("/user")
    public User createUser(@RequestBody User user) {
        // 打印接收到的用户信息
        System.out.println("Received user: " + user.getName());
        // 处理逻辑,例如保存用户信息到数据库
        return user; // 返回接收到的用户对象,自动转换为JSON格式
    }
}

总结

  • 引入依赖:确保项目中包含 Jackson 库(三大件)。
  • 配置 Spring MVC:通常不需要手动配置,如果需要自定义配置,可以添加 MappingJackson2HttpMessageConverter
  • 控制器返回 JSON 数据:使用 @ResponseBody@RestController 注解。
  • 接收 JSON 请求数据:使用 @RequestBody 注解将请求中的 JSON 数据绑定到方法参数上。

SpringMvc 拦截器

在 Spring MVC 中,拦截器(Interceptor)是一种强大的机制,它允许开发者在处理器执行请求前后,进行预处理和后处理。拦截器可以用于实现诸如日志记录、权限检查、跨域处理等功能,它与过滤器(Filter)不同,拦截器更加专注于处理请求的业务逻辑。

Spring MVC 拦截器的工作原理

拦截器是由 Spring MVC 框架提供的,它基于 Java 的反射机制,允许开发者自定义一个拦截器类,并通过配置使其在特定的请求前后执行预设的方法。

实现一个拦截器

要实现一个拦截器,需要实现 HandlerInterceptor 接口,并覆写其方法。通常情况下,需要实现以下三个方法:

  1. preHandle:在请求处理之前调用。返回值决定是否继续处理请求。
  2. postHandle:在请求处理之后调用,但是在视图渲染之前。可以对 ModelAndView 进行操作。
  3. afterCompletion:在整个请求完成之后,即视图渲染完毕后调用。可以用于资源清理工作。

示例:实现一个日志记录拦截器

以下是一个简单的拦截器示例,用于记录请求的处理时间和 URL。

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

public class LoggingInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        long startTime = System.currentTimeMillis();
        request.setAttribute("startTime", startTime);
        System.out.println("Request URL::" + request.getRequestURL().toString() + " Sent to Handler :: Current Time=" + System.currentTimeMillis());
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("Request URL::" + request.getRequestURL().toString() + " Post Handle called :: Current Time=" + System.currentTimeMillis());
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        long startTime = (Long) request.getAttribute("startTime");
        System.out.println("Request URL::" + request.getRequestURL().toString() + " Request Completed :: Current Time=" + System.currentTimeMillis());
    }
}

注册拦截器

要使拦截器生效,需要将其注册到 Spring MVC 的配置中。可以通过 Java 配置或 XML 配置来完成。

Java 配置方式
import org.springframework.context.annotation.Bean;
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 AppConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoggingInterceptor());
    }
}
XML 配置方式
<mvc:interceptors>
    <bean class="com.example.LoggingInterceptor" />
</mvc:interceptors>

注意事项

  • 拦截器可以按照添加的顺序依次执行,可以通过 addInterceptor 方法的顺序控制拦截器的执行顺序。
  • 拦截器不像过滤器可以直接操作 HttpServletResponse,只能通过参数 HttpServletResponse 进行间接操作。
  • 如果拦截器的 preHandle 方法返回 false,则后续的处理链被中止,即请求不会继续被处理,且不会调用 postHandleafterCompletion 方法。

通过使用拦截器,可以实现更细粒度的请求处理控制和业务逻辑的解耦,提高了代码的复用性和可维护性。

SpringMvc 文件上传

在 Spring MVC 中实现文件上传是一种常见的需求,它允许用户将文件上传到服务器并进行处理。Spring MVC 提供了简便的方式来处理文件上传,包括配置文件上传解析器和编写处理文件上传的控制器方法。

步骤概述

下面是在 Spring MVC 中实现文件上传的基本步骤:

  1. 添加依赖
  2. 配置文件上传解析器
  3. 编写文件上传表单
  4. 编写处理文件上传的控制器方法
添加依赖

首先,确保项目中包含必要的依赖,特别是 spring-webmvccommons-fileupload 或者 spring-boot-starter-web(如果使用 Spring Boot)。

Maven 依赖

如果是基于 Spring Boot 项目,通常只需要添加以下依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId> <!-- 或者 spring-boot-starter -->
</dependency>

如果是传统的 Spring MVC 项目,需要手动添加 commons-fileuploadcommons-io 依赖:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version> <!-- 或者最新版本 -->
</dependency>
<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.11.0</version> <!-- 或者最新版本 -->
</dependency>
配置文件上传解析器

如果使用 Spring Boot,它已经自动配置了文件上传解析器。对于传统的 Spring MVC 项目,需要手动配置 CommonsMultipartResolver(如果使用 Apache Commons FileUpload)或 StandardServletMultipartResolver(如果使用 Servlet 3.0+的标准文件上传解析器)。

Java 配置方式
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public CommonsMultipartResolver multipartResolver() {
        CommonsMultipartResolver resolver = new CommonsMultipartResolver();
        // 设置最大上传文件大小 (MB)
        resolver.setMaxUploadSize(5 * 1024 * 1024); // 5MB
        return resolver;
    }
}
编写文件上传表单

在前端页面(通常是 HTML 或者 Thymeleaf 模板)中编写文件上传表单,例如:

<form action="/upload" method="post" enctype="multipart/form-data">
    <input type="file" name="file" />
    <button type="submit">Upload File</button>
</form>
编写处理文件上传的控制器方法

编写控制器方法来处理文件上传,并根据需要对上传的文件进行处理。

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;

@Controller
public class FileUploadController {

    @PostMapping("/upload")
    @ResponseBody
    public String handleFileUpload(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return "Please select a file to upload";
        }

        try {
            // 保存文件到服务器的指定位置
            String uploadDir = "/path/to/upload/directory/";
            File destFile = new File(uploadDir + file.getOriginalFilename());
            file.transferTo(destFile);
            return "File uploaded successfully";
        } catch (IOException e) {
            e.printStackTrace();
            return "Failed to upload file";
        }
    }
}
处理多文件上传

如果需要处理多个文件上传,可以将 MultipartFile 数组作为控制器方法的参数:

@PostMapping("/uploadMultiple")
@ResponseBody
public String handleMultipleFileUpload(@RequestParam("files") MultipartFile[] files) {
    // 处理多文件上传逻辑
}

文件上传注意事项

  • 文件大小限制:可以通过配置文件上传解析器来限制上传文件的大小。
  • 文件存储路径:确保上传的文件存储在安全可控的位置,避免直接存储在 Web 应用程序根目录下。
  • 异常处理:处理文件上传时可能出现的 IOException 或其他异常,确保在上传失败时提供友好的错误信息。

通过上述步骤,你可以在 Spring MVC 中实现简单且有效的文件上传功能,满足各种 Web 应用程序的需求。

SSH/SSM 整合

SSH 整合的基本思路

配置 Spring 框架

Spring 框架作为整合的核心,负责管理 Bean、事务、AOP 等。配置类似于之前 SSH 的配置,但针对 Spring MVC 进行一些调整:

  • 配置 Spring 容器:定义 Spring 的配置文件,通常是 applicationContext.xml
  • 配置数据源:定义数据库连接池和 Hibernate 的 SessionFactory,如前面 SSH 整合中配置的方式。
  • 配置事务管理器:定义 Spring 的事务管理器,确保 Hibernate 的事务能够被 Spring 管理。

示例配置文件 applicationContext.xml

<!-- 数据源配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/mydatabase" />
    <property name="username" value="root" />
    <property name="password" value="password" />
</bean>

<!-- 配置Hibernate的SessionFactory -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="com.example.model" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.MySQL8Dialect</prop>
            <prop key="hibernate.show_sql">true</prop>
        </props>
    </property>
</bean>

<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>

<!-- 启用Spring的注解和事务管理 -->
<context:component-scan base-package="com.example">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<tx:annotation-driven />

配置 Spring MVC 框架

Spring MVC 负责 Web 层的 MVC 模式实现,替代 Struts 框架,配置方式较为简单和灵活:

  • 配置 DispatcherServlet:Spring MVC 的核心控制器,处理所有的请求分发和处理。
  • 配置 HandlerMapping:定义 URL 到 Controller 的映射关系。
  • 配置 ViewResolver:定义视图解析器,解析 Controller 方法返回的逻辑视图名。

示例配置文件 springmvc-servlet.xml

<!-- 配置DispatcherServlet -->
<servlet>
    <servlet-name>springmvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/springmvc-servlet.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>

<!-- 映射DispatcherServlet的URL路径 -->
<servlet-mapping>
    <servlet-name>springmvc</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

<!-- 配置Spring MVC扫描控制器 -->
<context:component-scan base-package="com.example.controller" />

<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>

编写 Controller 和视图

在 Spring MVC 中,Controller 处理请求并返回 ModelAndView 对象或其他响应体。视图解析器将逻辑视图名解析为具体的视图文件。

示例 Controller 类 UserController.java

@Controller
@RequestMapping("/user")
public class UserController {

    @Autowired
    private UserService userService;

    @GetMapping("/list")
    public String listUsers(Model model) {
        List<User> users = userService.getAllUsers();
        model.addAttribute("users", users);
        return "user/list"; // 返回逻辑视图名,由视图解析器解析为实际的JSP视图路径
    }

    // 其他控制器方法,如新增、编辑、删除用户等
}

整合配置

确保 Spring、Spring MVC 和 Hibernate 能够顺利整合,通过配置文件和注解来定义 Bean、配置数据源和事务管理,以及编写 Controller 来处理用户请求和响应。整合后的 SSM 框架能够有效地支持复杂的 Java Web 应用程序开发,并且具有良好的灵活性和可维护性。

Spring 容器和 SpringMvc 容器的关系

在 Spring 框架中,Spring 容器(ApplicationContext)和 Spring MVC 容器(DispatcherServlet)是两个核心的容器,它们在整个应用程序中扮演不同的角色,但彼此之间也有一定的关系。

Spring 容器(ApplicationContext)

Spring 容器负责管理应用程序中的所有 Bean 对象及其依赖关系,它是整个 Spring 框架的核心。Spring 容器主要有以下特点和功能:

  • 管理 Bean:负责创建、管理和装配 Bean,通过依赖注入(Dependency Injection,DI)将 Bean 组装起来。
  • 提供 AOP 支持:实现面向切面编程(Aspect-Oriented Programming,AOP),例如事务管理、日志记录等。
  • 集成不同层次的组件:可以整合数据访问、业务逻辑和 Web 层组件。
  • 提供声明式事务管理:通过声明式的方式管理事务,简化了事务管理的配置和使用。

Spring 容器可以通过 XML 配置文件、Java 注解或 Java 代码来定义和配置 Bean,最常见的是通过 XML 配置文件(如 applicationContext.xml)进行配置。

Spring MVC 容器(DispatcherServlet)

Spring MVC 容器是基于 Spring 框架的一个模块,用于支持 Web 应用程序的开发,它扩展了 Spring 的核心容器,具有以下主要功能:

  • 处理 Web 请求:拦截 HTTP 请求,并将其分发给相应的处理器(Controller)进行处理。
  • 定义视图解析器:将处理器(Controller)的逻辑视图名解析为具体的视图对象(如 JSP 页面)。
  • 提供数据绑定:将 HTTP 请求的参数绑定到 Controller 的方法参数或对象中。
  • 支持多种视图技术:如 JSP、Thymeleaf 等,以及 RESTful 风格的 JSON/XML 响应。

Spring MVC 容器由 DispatcherServlet 负责初始化和管理,它可以配置多个拦截器、多个处理器映射器(HandlerMapping)和多个视图解析器等,以支持不同的 Web 请求处理需求。

关系与整合

  1. 独立但互为补充
    • Spring 容器和 Spring MVC 容器是相互独立的,各自管理不同层次和领域的 Bean 对象。
    • Spring 容器负责整个应用程序的 IoC 和 AOP 管理,包括业务逻辑和数据访问层的 Bean。
    • Spring MVC 容器专注于 Web 层的请求处理、视图解析和控制器管理。
  2. 整合方式
    • 在 Spring MVC 应用中,通常会存在一个全局的 Spring 容器(ApplicationContext),负责整个应用的业务逻辑和数据访问的 Bean 管理。
    • 每个 DispatcherServlet 实例也有自己的 WebApplicationContext,它是 Spring MVC 的核心容器,专门用于管理 Web 相关的 Bean,例如 Controller、拦截器等。
  3. 协同工作
    • Spring 容器和 Spring MVC 容器可以协同工作,例如 Spring 容器可以通过上下文加载器(ContextLoaderListener)加载应用程序的全局上下文,而 Spring MVC 容器通过 DispatcherServlet 加载 Web 应用程序的上下文。
    • Spring MVC 容器可以访问 Spring 容器中定义的 Bean,从而实现业务逻辑和数据访问层与 Web 层的整合。

综上所述,Spring 容器和 Spring MVC 容器在 Spring 框架中扮演不同角色,各自专注于不同的领域和层次的管理和处理,通过适当的配置和整合,能够共同支持一个完整的 Spring MVC 应用程序的开发和运行。