Intro

这里是小郭的Java册子,涵盖Java开发中使用的技术。

Spring

简化开发,降低企业级开发的复杂性
框架整合,高效整合其他技术,提高企业级应用开发与运行效率

简化开发

  • IoC
  • AOP
  • 事务处理

框架整合

  • MyBatis
  • MyBatis-plus
  • Struts
  • Struts2
  • Hibernate

Spring Framework系统架构

Data Access: 数据访问 Data Integration: 数据集成 包容其他技术 Transactions: 事务控制方案

Web: Web开发

Core Container: 核心容器 管理对象

AOP: 面向切面编程 增强功能 Aspects: AOP思想实现 对AOP思想进行实现

Test: 单元测试与集成测试

第一部分:核心容器 核心概念(IoC/DI) 容器基本操作

第二部分:整合 整合数据层技术MyBatis

第三部分:AOP 核心概念 AOP基本操作 AOP实用开发

第四部分:事务 事务实用开发

第五部分:家族 SpringMVC SpringBoot SpringCloud

核心概念

IoC/DI

修改数据层实现代码后,还需要修改业务层实现代码

代码书写耦合度偏高 实用对象时,在程序中不要主动使用new产生对象,转换为由外部提供对象

  • IoC(Inversion of Control) 控制反转

    • 对象的创建控制权由程序转移到外部,这种思想称为控制反转
  • Spring 技术对 IoC 思想进行了实现

    • Spring提供了一个容器,称为IoC容器,用来充当IoC思想中的“外部”
    • IoC容器负责对象的创建、初始化等一系列工作,被创建或被管理的对象在IoC容器中统称为Bean
  • DI 依赖注入

    • 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

最终效果:

  • 使用对象时不仅可以直接从IoC容器中获取,并且获取到的bean已经绑定了所有的依赖关系

IoC入门案例

  1. 管理什么?(Service与Dao)
  2. 如何将被管理的对象告知IoC容器?(配置)
  3. 被管理的对象交给IoC容器,如何获取到IoC容器?(接口)
  4. IoC容器得到后,如何从容器中获取bean?(接口方法)
  5. 使用Spring导入哪些坐标?(pom.xml)
#pom.xml

# 1. 导入spring的坐标spring-context. 对应版本是5.2.10.RELEASE
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>
# applicationContext.xml

# 2. 配置bean
# bean 标签表示配置Bean
# id属性表示给bean起名字
# class属性表示给bean定义类型

<bean id="bookDao1" class="com.xxx.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl"/>
# App.java
public staic void main(String[] args){

    // 获取IoC容器
    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 获取bean
    BookDao bookDao = (BookDao)ctx.getBean("bookDao");

    bookDao.save();
}

DI入门案例

  1. 基于IoC管理Bean
  2. Service中使用new形式创建的Dao对象是否保留?(否)
  3. Service中需要的Dao对象如何进入到Service中?(提供方法)
  4. Service与Dao间的关系如何描述?(配置)
# BookServiceImpl.java
public class BookServiceImpl implements bookService{
    // 删除业务层中使用new的方式创建的dao对象
    private BookDao bookDao = new BookDaoImpl();

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }

    // 提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}
# applicationContext.xml


<bean id="bookDao1" class="com.xxx.dao.impl.BookDaoImpl"/>

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    # 配置service与dao的关系
    # property标签表示配置当前bean的属性
    # name属性表示配置哪一个具体的属性
    # ref属性表示参照哪一个bean
    <property name="bookDao" ref="bookDao1"/>
</bean>

Bean配置

id

功能:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一

class

功能:bean的类型,即配置的bean的全路径类名

name

功能:定义bean的别名,可定义多个,使用 , ; 进行分隔

scope

功能: 定义bean的作用范围,可选范围如下

  • singleton:单例(默认)
  • prototype:非单例

范例

<bean id="bookDao" class="com.xxx.dao.impl.BookDaoImpl" scope="prototype" />

适合交给容器进行管理的Bean

  • 表现层对象
  • 业务层对象
  • 数据层对象
  • 工具对象

不适合交给容器进行管理的Bean

  • 封装实体的域对象

Bean实例化

构造方法

public class BookDaoImpl implements BookDao{
    public BookDaoImpl(){

    }
    public void save(){
        System.out.println("book dao save ...");
    }
}
<bean id="bookDao" class="com.xxx.dao.impl.BookDaoImpl" />

使用构造方法,私有构造方法也会被调用,使用的反射 无参构造方法如果不存在,将抛出异常 BeanCreationException

静态工厂实例化Bean

public class OrderDaoFactory {
    public static OrderDao getOrderDao(){
        return new OrderDaoImpl();
    }
}

<bean id="orderDao" class="com.xxx.factory.OrderDaoFactory" factory-method="getOrderDao"/>

实例工厂实例化Bean

<bean id="UserFactory" class="com.xxx.factory.UserDaoFactory" />
<bean id="UserDao" factory-method="getUserDao" factory-bean="userFactory" />
public class UserDaoFactory {
    public UserDao getUserDao() {
        return new UserDaoImpl();
    }
}

使用FactoryBean实例化Bean

public class UserDaoFactoryBean implements FactoryBean<UserDao>{
    public UserDao getObject() throws Exception {
        return new UserDaoImpl();
    }

    public Class<?> getObjectType() {
        return UserDao.class;
    }

    // public boolean isSingleton(){
    //   return false;
    // }
}
<bean id="userDao" class="com.xxx.factory.UserDaoFactoryBean" />

Bean生命周期

生命周期:Bean从创建到销毁的过程
生命周期控制:在bean创建后到销毁前做一些事情

public class BookDaoImpl implements BookDao{
    public void save(){
        System.out.println("book dao save ...");
    }

    // 表示bean初始化对应的操作
    public void init(){
        // 加载资源,文件等初始化的操作
        System.out.println(" ... ")
    }

    // 表示bean销毁前对应的操作
    public void destroy(){

    }
}
<bean id="bookDao" class="com.xxx.dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
public staic void main(String[] args){

    // 获取IoC容器
    ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    // 获取bean
    BookDao bookDao = (BookDao)ctx.getBean("bookDao");

    bookDao.save();
    ctx.registerShutdownHook(); // 注册关闭钩子 关虚拟机之前先关闭容器
    // ctx.close(); // 手动关闭容器,暴力一些
}

使用接口控制生命周期的方式

实现 InitializingBeanDisposableBean 接口
该方式不需要在配置中设置 init-methoddestroy-method

# BookServiceImpl.java
public class BookServiceImpl implements bookService, InitializingBean, disposablebean{
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao){
        System.out.print("set .....");
        this.bookDao = bookDao;
    }

    public void save() {
        System.out.println("book service save ...");
        bookDao.save();
    }

    public void destroy() throws Exception {
        System.out.println("service destroy");
    }

    public void afterPropertiesSet() throws Exception{
        System.out.println("service init");
    }

    // 提供对应的set方法
    public void setBookDao(BookDao bookDao) {
        this.bookDao = bookDao;
    }
}

生命周期阶段

  • 初始化容器
    1. 创建对象(内存分配)
    2. 执行构造方法
    3. 执行属性注入(set操作)
    4. 执行bean初始化方法
  • 使用bean
    1. 执行业务操作
  • 关闭/销毁容器
    1. 执行bean销毁方法

依赖注入方式

思考:向一个类中传递数据的方式有几种?

  • 普通方法(set方法)
  • 构造方法

思考:依赖注入描述了在容器中建立bean与bean之间依赖关系的过程,如果bean运行需要的是数字或字符串呢?

  • 引用类型
  • 简单类型(基本数据类型与String)

依赖注入方法:

  • setter 注入
    • 简单类型
    • 引用类型
  • 构造器注入
    • 简单类型
    • 引用类型

setter 注入——引用类型

在bean中定义引用类型属性并提供可访问的set方法

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;

    public void setUserDao(UserDao userDao){
        this.userDao = userDao;
    }

    public void setBookDao(BookDao bookDao){
        this.bookDao = bookDao;
    }

    public void save(){
        System.out.println("book service save ...");
        bookDao.save;
        userDao.save;
    }
}

配置中使用property标签ref属性注入引用类型对象

<bean id ="bookDao" class="com.xxx.dao.impl.BookDaoImpl"/>
<bean id ="userDao" class="com.xxx.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
    <property name="userDao" ref="userDao"/>
</bean>

setter 注入——简单类型

在bean中定义简单类型属性并提供可访问的set方法

public class BookDaoImpl implements BookDao {

    private int connectionNum;
    private String databaseName;

    public void setConnectionNum(int connectionNum){
        this.connectionNum = connectionNum;
    }

    public void setDatabaseName(String databaseName){
        this.databaseName = databaseName;
    }

    public void save(){
        System.out.println("book dao save ..." + databaseName + "," + connectionNum);
    }
}

配置中使用property标签value属性输入简单类型数据

<bean id ="bookDao" class="com.xxx.dao.impl.BookDaoImpl">
    <property name="databaseName" value="mysql" />
    <property name="connectionNum" value="10" /> // 类型自动转
</bean>
<bean id ="userDao" class="com.xxx.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    <property name="bookDao" ref="bookDao"/>
    <property name="userDao" ref="userDao"/>
</bean>

构造器注入——引用类型(了解)

在bean中定义引用类型属性并提供可访问的构造方法

public class BookServiceImpl implements BookService{
    private BookDao bookDao;
    private UserDao userDao;

    public BookServiceImpl(BookDao bookDao1, UserDao userDao1){
        this.bookDao = bookDao1;
        this.userDao = userDao1;
    }

    public void save(){
        System.out.println("book service save ...");
        bookDao.save;
        userDao.save;
    }
}

配置中使用constructor-arg标签ref属性注入引用类型对象

<bean id ="bookDao" class="com.xxx.dao.impl.BookDaoImpl"/>
<bean id ="userDao" class="com.xxx.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao1" ref="bookDao" /> // name 为形参的名
    <constructor-arg name="userDao1" ref="userDao" /> // name 为形参的名
</bean>

构造器注入——简单类型(了解)

在bean中定义简单类型属性并提供可访问的构造方法

public class BookDaoImpl implements BookDao {

    private int connectionNum;
    private String databaseName;

    public BookDaoImpl(String databasename, int connectionNum){
        this.connectionNum = connectionNum;
        this.databaseName = databaseName;
    }
    public void save(){
        System.out.println("book dao save ..." + databaseName + "," + connectionNum);
    }
}

配置中使用constructor-arg标签value属性注入简单类型数据

<bean id ="bookDao" class="com.xxx.dao.impl.BookDaoImpl">
    <constructor-arg name="databaseName" value="mysql"/> // name 为形参的名
    <constructor-arg name="connectionNum" value="10"/> // name 为形参的名
</bean>
<bean id ="userDao" class="com.xxx.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao" />
    <constructor-arg name="userDao" ref="userDao" />
</bean>

构造器注入——参数适配(了解)

配置中使用constructor-arg标签type属性设置按形参类型注入

// 解决形参名称的问题,与形参名不耦合
<bean id ="bookDao" class="com.xxx.dao.impl.BookDaoImpl">
    <constructor-arg type="java.lang.String" value="mysql"/>
    <constructor-arg type="int" value="10"/>
</bean>
<bean id ="userDao" class="com.xxx.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao" />
    <constructor-arg name="userDao" ref="userDao" />
</bean>

配置中使用constructor-arg标签index属性设置按形参位置注入

// 解决参数的类型重复问题,使用位置解决参数匹配
<bean id ="bookDao" class="com.xxx.dao.impl.BookDaoImpl">
    <constructor-arg index="0" value="mysql"/>
    <constructor-arg index="1" value="10"/>
</bean>
<bean id ="userDao" class="com.xxx.dao.impl.UserDaoImpl"/>

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao" />
    <constructor-arg name="userDao" ref="userDao" />
</bean>

依赖注入方式选择

  1. 强制依赖使用构造器进行,使用setter注入有概率不进行注入导致null对象出现
  2. 可选依赖使用setter注入进行,灵活性强
  3. Spring框架倡导使用构造器,第三方框架内部大多数采用构造器注入的形式进行数据初始化,相对严谨
  4. 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选依赖的注入
  5. 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入
  6. 自己开发的模块推荐使用setter注入

依赖自动装配

IoC容器根据Bean所依赖的资源在容器中自动查找并注入到Bean中的过程称为自动装配

自动装配方式:

  • 按类型(常用)
  • 按名称
  • 按构造方法
  • 不启用自动装配

配置中使用bean标签autowire属性设置自动装配的类型

// applicationContext.xml

<bean class="com.xxx.dao.impl.BookDaoImpl" />
<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl" autowire="byType"/>

// applicationContext.xml

<bean name="bookDao" class="com.xxx.dao.impl.BookDaoImpl" />
<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl" autowire="byName"/> // 匹配的是 set 方法去掉'set'的名称

public class BookServiceImpl implements BookService{
    private BookDao bookDao;

    public void setBookDao(BookDao bookDao){
        this.bookDao = bookDao;
    }

    public void save(){
        System.out.println("book service save ...");
        bookDao.save();
    }
}
  • 自动装配用于引用类型依赖注入,不能对简单类型进行操作

  • 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用

  • 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名与配置耦合,不推荐使用

  • 自动装配优先级低于setter注入与构造器注入,同时出现时自动装配配置失效

集合注入

数组 List Set Map Properties


<bean id="bookDao" class="com.xxx.dao.impl.BookDaoImpl">
    <property name="array">
        <array>
            <value>100</value>
            <value>200</value>
            <value>300</value>
            // <ref bean="beanId"/>
        </array>
    </property>
    <property name="list">
        <list> // array 和 list 可以混用
            <value>itcast</value>
            <value>itheima</value>
            <value>boxuegu</value>
        </list>
    </property>
    <property name="set">
        <set>
            <value>itcast</value>
            <value>itheima</value>
            <value>boxuegu</value>
            <value>boxuegu</value> // 自动过滤
        </set>
    </property>
    <property name="map">
        <map>
            <entry key="country" value="china" />
            <entry key="province" value="henan" />
            <entry key="city" value="kaifeng" />
        </map>
    </property>
    <property name="properties"?
        <props>
            <prop key="country">china</prop>
            <prop key="province">henan</prop>
            <prop key="city">kaifeng</prop>
        </props>
    </property>
</bean>

案例:数据源对象管理

druid

导入druid坐标

# pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring.context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactid>
        <version>1.1.16</version>
    </dependency>
</dependencies>

配置数据源对象作为spring管理的bean

// applicationContext.xml
<bean id="dataSource" class="com.xxx.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc://localhost:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
# App.java
public class App{
    public static void main(String[] args){
        applicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        DataSource dataSource = (DataSource) ctx.getBean("dataSource");
        System.out.println(dataSource);
    }
}

c3p0

# pom.xml
<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring.context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactid>
        <version>1.1.16</version>
    </dependency>
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactid>
        <version>0.9.1.2</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactid>
        <version>5.1.16</version>
    </dependency>

</dependencies>
// applicationContext.xml
<bean class="com.xxx.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc://localhost:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
</bean>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc://localhost:3306/spring_db"/>
    <property name="user" value="root"/>
    <property name="password" value="root"/>
    <property name="maxPoolSize" value="1000"/>
</bean>

加载properties文件

// resources/jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/spring_db
jdbc.username=root
jdbc.password=root

修改前Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<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">

</beans>

修改后

<?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->xmlns:context beans->context
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans  // 复制该行
           http://www.springframework.org/schema/beans/spring-beans.xsd  // 复制该行
           http://www.springframework.org/schema/context  // 粘贴 beans->context
           http://www.springframework.org/schema/context/spring-context.xsd  // 粘贴 beans->context
           ">

// 1. 开启context命名空间

// 2. 使用context空间加载properties文件
    <context:property-placeholder location="jdbc.properties"/>
    // <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/> // 不加载系统属性

// 3. 使用属性占位符${}读取properties文件中的属性

    <bean id="dataSource" class="com.xxx.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

测试一下

public class BookDaoImpl implements BookDao {
    private String name;

    public void setName(String name){
        this.name = name;
    }

    public void save(){
        System.out.println("book dao save ..." + name);
    }
}
<bean id="bookDao" class="com.xxx.dao.impl.BookDaoImpl">
    <property name="name" value="${jdbc.driver}"/>
</bean>
public class App{
    public static void main(String[] args){
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        bookDao.save();
    }
}

加载多个配置文件

<?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->xmlns:context beans->context
       xsi:schemaLocation="
           http://www.springframework.org/schema/beans  // 复制该行
           http://www.springframework.org/schema/beans/spring-beans.xsd  // 复制该行
           http://www.springframework.org/schema/context  // 粘贴 beans->context
           http://www.springframework.org/schema/context/spring-context.xsd  // 粘贴 beans->context
           ">

// 1. 开启context命名空间

// 2. 使用context空间加载properties文件
    // 加载多个properties文件
    <context:property-placeholder location="jdbc.properties,jdbc2.properties"/>
    // 加载所有properties文件
    <context:property-placeholder location="*.properties"/>
    // 加上类路径
    <context:property-placeholder location="classpath:*.properties"/>
    // 不仅从当前工程中读,还从依赖的jar包中读
    <context:property-placeholder location="classpath*:*.properties"/>
    // <context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/> // 不加载系统属性

// 3. 使用属性占位符${}读取properties文件中的属性

    <bean id="dataSource" class="com.xxx.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

容器

public class App{
    public static void main(String[] args){
        // 1. 加载类路径下的配置文件
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        // 2. 从文件系统下加载配置文件
        // ApplicationContext ctx = new FileSystemXmlApplicationContext("/Users/guo/Code/spring-demo/src/main/resources/applicationContext.xml");

        // BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        // BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        bookDao.save();
    }
}

创建容器

方式一: 类路径加载配置文件

ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");

方式二:文件路径加载配置文件

ApplicationContext ctx = new FileSystemXmlApplicationContext("/Users/guo/Code/spring-demo/src/main/resources/applicationContext.xml");

方式三:加载多个配置文件

ApplicationContext = ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");

获取Bean

方式一:使用Bean名称获取

BookDao bookDao = (BookDao) ctx.getBean("bookDao");

方式二:使用Bean名称获取并指定类型

BookDao bookDao = ctx.getBean("bookDao", BookDao.class);

方式三:使用Bean类型获取

BookDao bookDao = ctx.getBean(BookDao.class);

容器类层次结构

BeanFactory初始化

类路径加载配置文件

public class AppForBeanFactory{
    public static void main(String[] args){
        Resource resources = new ClassPathResource("applicationContext.xml");
        BeanFactory bf = new XmlBeanFactory(resources);
        BookDao bookDao = bf.getBean(BookDao.class);
        bookDao.save();
    }
}

BeanFactory创建完毕后,所有的Bean均为延迟加载,Bean的构造方法不会触发

ApplicationContext 延迟加载

<bean id="bookDao" class="com.xxx.dao.impl.BookDaoImpl" lazy-init="true"/>

核心容器总结

容器相关

  • BeanFactory是IoC容器的顶层接口,初始化BeanFactory对象时,加载的Bean延迟加载
  • ApplicationContext接口是Spring容器的核心接口,初始化时Bean立即加载
  • ApplicationContext接口提供基础的Bean操作相关方法,通过其他接口扩展其功能
  • ApplicationContext接口常用初始化类
    • ClassPathXmlApplicationContext
    • FileSystemXmlApplicationContext

Bean相关

<bean
    id="bookDao"                                    bean的Id
    name="dao bookDaoImpl daoImpl"                  bean的别名
    class="com.xxx.dao.impl.BookDaoImpl"            bean类型,静态工厂类,FactoryBean类
    scope="singleton"                               控制bean的实例数量
    init-method="init"                              生命周期初始化方法
    destroy-method="destroy"                        生命周期销毁方法
    autowire="byType"                               自动装配类型
    factory-method="getInstance"                    bean工厂方法,应用于静态工厂或实例工厂
    factory-bean="com.xxx.factory.BookDaoFactory"   实例工厂bean
    lazy-init="true"                                控制bean延迟加载
/>

依赖注入相关

<bean id="bookService" class="com.xxx.service.impl.BookServiceImpl">
    <constructor-arg name="bookDao" ref="bookDao"/>                         构造器注入引用类型
    <constructor-arg name="userDao" ref="userDao"/>
    <constructor-arg name="msg" ref="WARN"/>                                构造器注入简单类型
    <constructor-arg name="java.lang.String" index="3" value="WARN"/>       类型匹配与索引匹配
    <property name="bookDao" ref="bookDao"/>                                setter注入引用类型
    <property name="userDao" ref="userDao"/>
    <property name="msg" ref="WARN"/>                                       setter注入简单类型
    <property name="names">                                                 setter注入集合类型
        <list>                                                              list集合
            <value>itcast</value>                                           集合注入简单类型
            <ref bean="dataSource"/>                                        集合注入引用类型
        </list>
    </property>
</bean>

注解开发

注解开发定义bean

<bean id="bookDao" class="com.xxx.dao.impl.BookDaoImpl"/>

以下代码等价于在配置文件中配置Bean,也就是以上的配置

使用@Component定义bean

@Component("bookDao")
public class BookDaoImpl implements BookDao{
    public void save(){
        System.out.println("book dao save ...");
    }
}

核心配置文件中通过组件扫描加载bean

<context:component-scan base-package="com.xxx.dao.impl"/>

Spring提供@Component注解的三个衍生注解

  • @Controller:用于表现层bean定义
  • @Service:用于业务层bean定义
  • @Repository:用于数据层bean定义
@Repository("bookDao")
public class BookDaoImpl implements BookDao{

}
@Service
public class BookServiceImpl implements BookService{

}

纯注解开发

@Configuration
@ComponentScan("com.xxx")  // 作用等同于 <context:component-scan ...
public class SpringConfig{

}
public class AppForAnnotation {
    public static void main(String[] args){
        // 加载配置类初始化容器
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = (BookDao) ctx.getBean("bookDao");
        System.out.println(bookDao);
        BookService bookService = ctx.getBean(BookService.class);
        System.out.println(bookService);
    }
}
  • Spring3.0升级了纯注解开发模式,使用Java类替代配置文件,开启了Spring快速开发赛道
  • 读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
  • @Configuration注解用于设定当前类为配置类
  • @ComponentScan注解用于设定扫描路径,此注解只能添加一次,多个数据请用数组格式
@ComponentScan({"com.xxx.service","com.xxx.dao"})

Bean管理

Bean作用范围

使用@Scope("singleton")定义Bean作用范围

@Repository
@Scope("singleton")
public class BookDaoImpl implments BookDao{
    public void save(){
        System.out.println("book dao save ...");
    }

    @PostConstruct
    public void init(){
        System.out.println("init ...");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("destroy ...");
    }
}

Bean生命周期

使用@PostConstruct、@PreDestroy定义Bean生命周期

依赖注入

自动装配

使用@Autowired注解开启自动装配模式(按类型)

@Service
public class BookServiceImpl implements BookService{
    @Autowired
    // @Qualifier("bookDao")
    private BookDao bookDao;

    // @Autowired // 放set方法也能用
    // public void setBookDao(BookDao bookDao){  // 不需要setter方法也能注入
    //     this.bookDao = bookDao;
    // }

    public void save(){
        System.out.println("book service save ...");
        bookDao.save();
    }
}
@Repository
public class BookDaoImpl implements BookDao{
    public void save(){
        System.out.println("book dao save ...");
    }
}

有多个数据层时,无法按类型装配,需要在@Repository()中指定名称,在需要注入的地方使用@Qualifier()输入名称指定要注入的Bean

注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter方法
注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法

使用@Qualifier注解开启指定名称装配Bean

@Service
public class BookServiceImpl implements BookService{
    @Autowired
    @Qualifier("bookDao")
    private BookDao bookDao;

注意:@Qualifier注解无法单独使用,必须配合@Autowired注解使用

使用@Value实现简单类型注入

@Repository
public class BookDaoImpl implements BookDao{
    @Value("bookname")
    private String name;

    public void save(){
        System.out.println("book dao save ..." + name);
    }
}

加载properties文件

使用@PropertySource注解加载properties文件

@Configuration
@ComponentScan("com.xxx")
@PropertySource("classpath:jdbc.properties") // 不支持通配符 *.properties
// @PropertySource({"jdbc.properties", "jdbc2.properties"}) // 多个配置文件使用数组列出
public class SpringConfig{

}

注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*

public class BookDaoImpl implements BookDao{
    @Value("${name}")
    private String name;

    public void save(){
        System.out.println("book dao save ..." + name);
    }
}
// jdbc.properties
name=bookname

第三方Bean管理

第三方Bean管理

写在Spring配置类中

@Configuration
public class SpringConfig{

    // 1.定义一个方法获得要管理的对象
    // 2.添加@Bean,表示当前方法的返回值是一个Bean
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUserName("root");
        ds.setPassword("root");
        return ds;
    }
}
public class App{
    public static void main(String[] args){
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        DataSource dataSource = ctx.getBean(DataSource.class);
        System.out.println(dataSource);
    }
}

使用独立的配置类管理第三方Bean

  • 方式一:导入式

将独立的配置类加入核心配置

public class JdbcConfig{
    // 1.定义一个方法获得要管理的对象
    // 2.添加@Bean,表示当前方法的返回值是一个Bean
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUserName("root");
        ds.setPassword("root");
        return ds;
    }
}

使用@Import注解手动加入配置类到核心配置,此注解只能添加一次,多个数据请用数组格式

@Configuration
@Import({JdbcConfig.class})
public class SpringConfig{

}
  • 方式二:扫描式
@Configuration
public class JdbcConfig{
    // 1.定义一个方法获得要管理的对象
    // 2.添加@Bean,表示当前方法的返回值是一个Bean
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring_db");
        ds.setUserName("root");
        ds.setPassword("root");
        return ds;
    }
}

使用@ComponentScan注解扫描配置类所在的包,加载对应的配置类信息

@Configuration
@ComponentScan({"com.xxx.config", "com.xxx.service", "com.xxx.dao"})
public class SpringConfig{

}

第三方Bean依赖注入

简单类型依赖注入

public class JdbcConfig{

    @Value("com.mysql.jdbc.Driver")
    private String driver;
    @Value("jdbc:mysql://localhost:3306/spring_db")
    private String url;
    @Value("root")
    private String userName;
    @Value("root")
    private String password;
    // 1.定义一个方法获得要管理的对象
    // 2.添加@Bean,表示当前方法的返回值是一个Bean
    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUserName(userName);
        ds.setPassword(password);
        return ds;
    }
}

引用类型依赖注入

@Bean
public DataSource dataSource(BookService BookService){
    System.out.println(bookService);
    DruidDataSource ds = new DruidDataSource();
    ds.setDriverClassName(driver);
    ds.setUrl(url);
    ds.setUserName(userName);
    ds.setPassword(password);
    return ds;
}

引用类型注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象

注解开发总结

XML配置与注解配置比较

整合MyBatis

MyBatis程序核心对象分析

// 1. 创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2. 加载SqlMapConfig.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
// 3. 创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
// 4. 获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 5. 执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
Account ac = accountDao.findById(1);
System.out.println(ac);
// 6. 释放资源
sqlSession.close();

整合MyBatis

<configuration>
    <properties resource="jdbc.properties"></properties>
    <typeAliases>
        <package name="com.xxx.domain"/>
    </typeAliases>
    <environments default="mysql">
        <environment id="mysql">
            <transactionManager type="JDBC"></transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"></property>
                <property name="url" value="${jdbc.url}"></property>
                <property name="username" value="${jdbc.username}"></property>
                <property name="password" value="${jdbc.password}"></property>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <package name="com.xxx.dao"></package>
    </mappers>
</configuration>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.3.0</version>
</dependency>
@Configuration
@ComponentScan("com.xxx")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
public class SpringConfig{

}
public class JdbcConfig{
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String userName;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource ds = new DruidDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUserName(userName);
        ds.setPassword(password);
        return ds;
    }
}
public class MybatisConfig{

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
        SqlSessionFactoryBean ssfb = new SqlSessionFactoryBean();
        ssfb.setTypeAliasesPackage("com.xxx.domain");
        ssfb.setDataSource(dataSource);
        return ssfb;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer(){
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.xxx.dao");
    }
}
public class App2{
    public static void main(String[] args){
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService accountService = ctx.getBean(AccountService.class);
        Account ac = accountService.findById(1);
        System.out.println(ac);
    }
}

整合Junit

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

使用Spring整合Junit专用的类加载器

//AccountServiceTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest{

    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){
        System.out.println(accountService.findById(2));
    }

    @Test
    public void testFindAll(){
        System.out.println(accountService.findAll());
    }
}

AOP简介

AOP核心概念

  • AOP(Aspect Oriented Programming)面向切面编程,一种编程范式,指导开发者如何组织程序结构
    • OOP (Object Oriented Programming)面向对象编程
  • 作用:在不惊动原始设计的基础上为其进行功能增强
  • Spring理念:无入侵式/无侵入式

alt text

  • 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等

    • 在SpringAOP中,理解为方法的执行
  • 切入点(Pointcut):匹配连接点的式子

    • 在SpringAOP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法

      • 一个具体方法:com. itheima.dao包下的BookDao接口中的无形参无返回值的save方法

      • 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法

  • 通知(Advice):在切入点处执行的操作,也就是共性功能

    • 在SpringAOP中,功能最终以方法的形式呈现
  • 通知类:定义通知的类

  • 切面(Aspect):描述通知与切入点的对应关系

AOP入门案例

AOP入门案例思路分析

案例设定:测定接口执行效率
简化设定:在接口执行前输出当前系统时间
开发模式:XML or 注解
思路分析:

  1. 导入坐标(pom.xml)
  2. 制作连接点方法(原始操作,Dao接口与实现类)
  3. 制作共性功能(通知类与通知)
  4. 定义切入点
  5. 绑定切入点与通知关系(切面)
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.10.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
</dependency>
public class BookDaoImpl implements BookDao{

    public void save(){
        System.out.println(System.currentTimeMillis());
        System.out.println("book dao save ...");
    }

    public void update(){
        System.out.println("book dao update ...");
    }
}
@Component
@Aspect
public class MyAdvice{

    // 定义切入点
    @Pointcut("execution(void com.xxx.dao.BookDao.update())")
    private void pt(){ }

    @Before("pt()") // 绑定切入点与通知关系
    public void method(){ // 定义共性功能
        System.out.println(System.currentTimeMillis());
    }
}
@Configuration
@ComponentScan("com.xxx")
@EnableAspectJAutoProxy
public class SpringConfig{

}

AOP工作流程

  1. Spring容器启动
  2. 读取所有切面配置中的切入点
  3. 初始化Bean,判定Bean对应的类中的方法是否匹配到任意切入点
    • 匹配失败,创建失败
    • 匹配成功,创建原始对象(目标对象)的代理对象
  4. 获取Bean执行方法
    • 获取Bean,调用方法并执行,完成操作
    • 获取的Bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
@Component
@Aspect
public class MyAdvice{

    @Pointcut("execution(void com.xxx.dao.BookDao.save())")
    private void ptx(){ }

    // 定义切入点
    @Pointcut("execution(void com.xxx.dao.BookDao.update())")
    private void pt(){ }

    @Before("pt()") // 绑定切入点与通知关系
    public void method(){ // 定义共性功能
        System.out.println(System.currentTimeMillis());
    }
}
public class App{
    public static void main(String[] args){
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        System.out.println(bookDao);
        System.out.println(bookDao.getClass());
    }
}

AOP核心概念

  • 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
  • 代理(Proxy):目标对象无法直接完成工作,需要对其进行功能回填,通过原始对象的代理对象实现

SpringAOP本质:代理模式

AOP切入点表达式

  • 切入点:要进行增强的方法
  • 切入点表达式:要进行增强的方法的描述方法
package com.xxx.dao;
public interface BookDao{
    public cvoid update();
}
public class BookDaoImpl implements BookDao{
    public void update(){
        System.out.println("book dao update ...");
    }
}

语法格式

描述方式一:执行com.xxx.dao包下的BookDao接口中的无参数update方法
execution (void com.xxx.dao. BookDao.update())

描述方式二:执行com.xxx.dao.imp1包下的BookDaoImpl类中的无参数update方法
execution (void com.xxx.dao.impl.BookDaoImpl.update())

切入点表达式标准格式:动作关键字(访问修饰符返回值包名.类/接口名.方法名(参数)异常名)

execution (public User com.itheima.service.UserService.findById (int))
  • 动作关键字:描述切入点的行为动作,例如execution 表示执行到指定切入点访问修饰符:public, private等,可以省略
  • 返回值
  • 包名
  • 类/接口名
  • 方法名
  • 参数
  • 异常名:方法定义中抛出指定异常,可以省略

通配符

可以使用通配符描述切入点,快速描述

  • *:单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现

    execution (public * com.itheima.*.UserService.find*(*))
    

    匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

  • ..:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写

    execution (public User com..UserService..findById (..)) // 可以多个参数
    

    匹配com包下的任意包中的UserService类或接口中所有名称为findByld的方法

  • +:专用于匹配子类类型

    execution(* *..*Service+.*(..))
    

书写技巧

  • 所有代码按照标准规范开发,否则以下技巧全部失效
  • 描述切入点通常描述接口,而不描述实现类
  • 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
  • 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配快速描述
  • 包名书写尽量不使用..匹配,效率过低,常用*做单个包描述匹配,或精准匹配
  • 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名
  • 方法名书写以动词进行精准匹配,名词采用*匹配,例如getByld书写成getBy*;selectAll书写成selectAll
  • 参数规则较为复杂,根据业务方法灵活调整
  • 通常不使用异常作为匹配规则

AOP通知类型

  • AOP通知描述了抽取的共性功能,根据共性功能抽取的位置不同,最终运行代码时要将其加入到合理的位置

  • AOP通知共分为5种类型

    • 前置通知
    • 后置通知
    • 环绕通知(重点)
    • 返回后通知(了解)
    • 抛出异常后通知(了解)
public interface BookDao{
    public void update();

    public int select();
}
@Aspect
public class MyAdvice{
    @Pointcut("execution(void com.xxx.dao.BookDao.update())")
    private void pt(){}

    @Pointcut("execution(int com.xxx.dao.BookDao.select())")
    private void pt2(){}


    @Before("pt()")
    public void before(){
        System.out.println("before advice ...");
    }

    @After("pt()")
    public void after(){
        System.out.println("after advice ...");
    }

    @Around("pt()")
    public void around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around before advice ...");
        // 表示对原始操作的调用
        pjp.proceed();
        System.out.println("around after advice ...");
    }

    @Around("pt2()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("around before advice ...");
        // 表示对原始操作的调用
        Object ret = pjp.proceed();
        System.out.println("around after advice ...");
        return ret;
    }

    @AfterReturning("pt2()")
    public void afterReturning(){
        System.out.println("afterReturning advice ...");
    }

    @AfterThrowing("pt2()")
    public void afterThrowing(){
        System.out.println("afterThrowing advice ...");
    }

}

案例:测量业务层接口万次执行效率

@Configuration
@ComponentScan("com.xxx")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class,MybatisConfig.class})
@EnableAspectJAutoProxy
public class SpringConfig{

}
@Component
@Aspect
public class ProjectAdvice{

    //匹配业务层的所有方法
    @Pointcut("execution(* com.xxx.service.*Service().*(..))")
    private void servicePt(){}

    @Around("ProjectAdvice.servicePt()")
    public void runSpeed(ProceedingJoinPoint pjp) throws Throwable{
        Signature signature = pjp.getSignature();
        String className = signature.getDeclaringTypeName();
        String methodName = signature.getName();

        long start = System.currentTimeMillis();
        for (int i = 0; i < 10000; i++){
            pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("万次执行: " + className + "." + methodName + "----->" + (end-start) + "ms");
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTestCase {
    @Autowired
    private AccountService accountService;

    @Test
    public void testFindById(){
        Account as = accountService.findById(2);
    }

    @Test
    public void testFindAll(){
        List<Account> all = accountService.findAll();
    }
}

AOP通知获取数据

  • 获取切入点方法的参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知
    • ProceedJointPoint:适用于环绕通知
  • 获取切入点方法返回值
    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息
    • 抛出异常后通知
    • 环绕通知
public interface BookDao{
    public String findName(int id);
}
@Repository
public class BookDaoImpl implements BookDao{
    public String findName(int id){
        System.out.println("id: " + id);
        return "itcast";
    }
}
public class App {
    public static void main(String[] args){
        ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
        BookDao bookDao = ctx.getBean(BookDao.class);
        String name = bookDao.findName(100);
        System.out.println(name);
    }
}
@Component
@Aspect
public class MyAdvice {
    @Pointcut("execution(* com.itheima.dao.BookDao.findName(..))")
    private void pt(){}

    @Before("pt()")
    public void before(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.printin("before advice ...");
    }

    @After("pt()")
    public void after(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.printin("after advice ...");
    }

    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        Object[] args = pjp.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = 666;
        Object ret = pjp.proceed(args);
       return ret;
    }

    @AfterReturning(value = "pt()", returning = "ret")
    public void afterReturning(JoinPoint jp, Object ret) { // JoinPoint 必须在 返回值参数之前
        System.out.printin("afterReturning advice ..." + ret);
    }

    @AfterThrowing(value = "pt()", throwing = "t")
    public void afterThrowing(Throwable t) {
        System.out. printin("afterThrowing advice ..." + t);
    }
}

获取参数

获取返回值

获取异常

案例:百度网盘密码数据兼容处理

@Repository
public class ResourceDaoImpl implements ResourcesDao{
    public boolean readResources(String url, String password) {
        // 模拟校验
        return password.equals("root");
    }
}
public class App{
    public static void main(String[] args) {
        ApplicationContext ctx = new AnnotationConfiaApplicationContext(springConfig.class);
        ResourceService resourceService = ctx.getBean(ResourceService.class);
        boolean flag = resourceService.openURL("http://pan.baidu.com/haha", "root ");
        System.out.println(flag);
    }
}
@Component
@Aspect
public class DataAdvice {
    @Pointcut("execution(boolean com.xxx.service.*Service.*(*,*))")
    private void servicePt(){}

    @Around("DataAdvice.servicePt()")
    public Object trimStr(ProceedingJoinPoint pjp){
        Object[] args = pjp.getArgs();
        for(int i = 0; i < args.length; i++){ // 不能使用forEach循环
            //判断参数是不是字符串
            if(args[i].getClass().equals(String.class)){
                args[i] = args[i].toString().trim();
            }
        }
        Object ret = pjp.proceed(args);
        return ret;
    }
}

AOP总结

  • 概念:AOP(Aspect Oriented Programming)面向切面编程,一种编程范式
  • 作用:在不惊动原始设计的基础上为方法进行功能增强
  • 核心概念
    • 代理(Proxy):SpringAOP的核心本质是采用代理模式实现的
    • 连接点(JoinPoint):在SpringAOP中,理解为任意方法的执行
    • 切入点(Pointcut):匹配连接点的式子,也是具有共性功能的方法描述
    • 通知(Advice):若干个方法的共性功能,在切入点处执行,最终体现为一个方法
    • 切面(Aspect):描述通知与切入点的对应关系
    • 目标对象(Target):被代理的原始对象成为目标对象

  • 切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名)
    • execution(* com.xxx.service.*Service.*(..))
  • 切入点表达式描述通配符:
    • 作用:用于快速描述,范围描述
    • *:匹配任意符号(常用)
    • ..:匹配多个连续的任意符号(常用)
    • +:匹配子类类型
  • 切入点表达式书写技巧
    1. 按标准规范开发
    2. 查询操作的返回值建议使用*匹配
    3. 减少使用..的形式描述包
    4. 对接口进行描述,使用*表示模块名,例如UserService的匹配描述为*Service
    5. 方法名书写保留动词,例如get,使用*表示名词,例如getById匹配描述为getBy*
    6. 参数根据实际情况灵活调整

  • 通知类型
    • 前置通知
    • 后置通知
    • 环绕通知(重点)
      • 环绕通知依赖形参ProceedingJoinPoint才能实现对原始方法的调用
      • 环绕通知可以隔离原始方法的调用执行
      • 环绕通知返回值设置为Object类型
      • 环绕通知中可以对原始方法调用过程中出现的异常进行处理
  • 返回后通知
  • 抛出异常后通知

  • 获取切入点方法的参数
    • JoinPoint:适用于前置、后置、返回后、抛出异常后通知,设置为方法的第一个形参
    • ProceedJoinPoint:适用于环绕通知
  • 获取切入点方法返回值
    • 返回后通知
    • 环绕通知
  • 获取切入点方法运行异常信息
    • 抛出异常后通知
    • 环绕通知

Spring事务简介

  • 事务作用:在数据层保障一系列的数据库操作同成功同失败
  • Spring事务作用:在数据层或业务层保障一系列的数据库操作同成功同失败
public interface PlatformTransactionManager{
    void commit(TransactionStatus status) throws TransactionException;
    void rollback(TransactionStatus status) throws TransactionException;
}
public class DataSourceTransactionManager {
    ...
}

public interface Accountservice {
    public void transfer(string out, string in, double money);
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;

    public void transfer(String out, String in, Double money) {
        accountDao.outMoney(out, money);
        int i = 1/0;   // 出现异常,异常之前的数据操作成功,之后的失败
        accountDao.outMoney(in, money);
    }
}
public interface AccountDao {
    @Update("update tbl_account set money = money + #{money} where name = #{name}")
    void inMoney(@Param("name") String name, @Param("money") Double money);

    @Update("update tbl_account set money = money - #{money} where name = #{name}")
    void outMoney(@Param("name") String name, @Param("money") Double money);
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom", "Jerry", 100D);
    }
}

使用事务管理

public interface Accountservice {
    @Transactional
    public void transfer(string out, string in, double money);
}
// JdbcConifg.class
    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
}

@Configuration
@ComponentScan( "com.xxx")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {}

}

Spring事务角色

  • 事务角色
    • 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
    • 事务协调员:加入事务方,在Spring中通常指代数据层方法,也可以是业务层方法

Spring事务属性

事务相关配置

public interface Accountservice {
    @Transactional(readOnly = true, timeout = - 1)
    public void transfer(string out, string in, double money);
}

只有 Error 和运行时异常,会触发回滚

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    public void transfer(String out, String in, Double money) throws IOException {
        accountDao.outMoney(out, money);
        // int i = 1/10;
        if(true){ throw new IOException(); } // 不回滚
        accountDao.inMoney(in, money);
    }
}
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer() throws IOException {
        accountService.transfer("Tom", "Jerry", 100D);
    }
}
public interface Accountservice {
    @Transactional(rollbackFor = {IOException.class)) // 对 IOException 回滚
    public void transfer(string out, string in, double money) throws IOException;
}

public interface LogService {
    @Transactional
    void log(String out, String in, Double money)
}
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Autowired
    private LogService logService;

    public void transfer(String out, String in, Double money) {
        try{
            accountDao.outMoney(out, money);
            int i = 1/0;
            accountDao.inMoney(in, money);
        }finally{
            logService.log(out, in, money);
        }
    }
}

日志记录同回滚

public interface LogService {
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    void log(String out, String in, Double money)
}

转账事务回滚,日志不回滚

事务传播行为

  • 事务传播行为:事务协调员对事务管理员所携带事务的处理态度

Spring MVC

SpringMvc概述

  • SpringMvc技术与Servlet技术功能等同,均属于web层开发技术
  • SpringMvc是一种基于Java实现MVC模型的轻量级web框架
  • 优点
    • 使用简单,开发便捷(相比于Servlet)
    • 灵活性强

Spring MVC 入门案例

<project>
    <dependencies>
        <!-- 1.导入坐标springmvc与servlet -->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmnv</artifactId>
            <version>5.2.10.RELEASES</version
        </dependency>
    </dependencies>
</project>
// 2. 定义controller
// 2.1 使用@Controller定义bean
@Controller
public class UserController {

    // 2.2设置当前操作的访问路径
    @RequestMapping("/save")
    // 2.3设置当前操作的返回值类型
    @ResponseBody
    public String save(){
        System.out.println("user save ...");
        return "{'module':'springmvc'}";
    }
}
// 3.创建springmvcde配置文件,加载controller对应的bean
@Configuration
@ComponentScan("com.xxx.controller")
public class SpringMvcConfig {

}
// 4. 定义一个servlet容器启动的配置类,在里面加载spring的配置
public class ServletContainersInitConif extends AbstractDispatcherServletInitializer {
    // 加载springMVC容器配置
    @Override
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConifg.class);
        return ctx;
    }

    // 设置哪些请求归属springMVC处理
    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    // 加载spring容器处理
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }
}
<!--配置tomacat插件-->
<project>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomct.maven</groupId>
                <artifactId>tomact7-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <port>80</port>
                    <path>/</path>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

入门案例工作流程

  • 启动服务器初始化过程
  1. 服务器启动,执行ServletContainersInitConf1g类,初始化web容器
  2. 执行createServletApplicationContext方法,创建了WebApplicationContext对象
  3. 加载SpringMvcConfig
  4. 执行@ComponentScan加载对应的bean
  5. 加载UserController,每个@RequestMapping的名称对应一个具体的方法
  6. 执行getServletMappings方法,定义所有的请求都通过SpringMVC
  • 单次请求过程
  1. 发送请求localhost/save
  2. web容器发现所有请求都经过SpringMVC,将请求交给SpringMVC处理
  3. 解析请求路径/save
  4. 由/save匹配执行对应的方法save()
  5. 执行save()
  6. 检测到有@ResponseBody直接将save()方法的返回值作为响应求体返回给请求方

Bean加载控制

Controller加载控制与业务bean加载控制

  • SpringMVC相关bean(表现层bean)

  • Spring控制的bean

    • 业务bean(service)
    • 功能bean(DataSource等)

因为功能不同,如何避免Spring错误的加载到SpringMVC的bean?

加载Spring控制的bean的时候排除掉SpringMVC控制的bean

  • SpringMVC相关bean加载控制

    • SpringMVC加载的bean对应的包均在com.xxx.controller包内
  • Spring相关bean加载控制

  • 方式一:Spring加载的bean设定扫描范围为com.xxx,排除掉controller包内的bean

  • 方式二:Spring加载的bean设定扫描范围为精准范围,例如service包、dao包等

  • 方式三:不区分Spring与SpringMVC的环境,加载到同一个环境中

方式一

@Configuration
@ComponentScan({"com.xxx.service", "com.xxx.dao"})
public class SpringConfig {

}

方式二

@Configuration
@ComponentScan(value = "com.xxx",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ANNOTATION,
        classes = Controller.class
    )
)
public class SpringConfig {

}
//@Configuration // 会被 SpringConfig 中的 ComponentScan 扫描到
@ComponentScan("com.xxx.controller")
public class SpringMvcConfig {

}

public class ServletContainersInitConfig extends AbstractDispatcherServletInitializer {
    protected WebApplicationContext createServletApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringMvcConfig.class);
        return ctx;
    }

    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();
        ctx.register(SpringConfig.class);
        return ctx;
    }

    protected Stringl[] getServletMappings() { return new String[]{"/"}; }
}

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigclasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigclasses() {
        return new class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

请求映射路径

@Controller
public class BookController {

    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("book save ... ");
        return "{'module':'book save'}";
    }
}
@Controller
public class UserController {

    @RequestMapping("/save")
    @ResponseBody
    public String save(){
        System.out.println("user save ... ");
        return "{'module':'user save'}";
    }

    @RequestMapping("/delete")
    @ResponseBody
    public String delete(){
        System.out.println("user delete ... ");
        return "{'module':'user delete'}";
    }

团队多人开发,美人设置不同的请求路径,冲突问题如何解决?

设置模块名作为请求路径前缀

请求发送普通参数

Get请求

http://localhost/commonParam?name=itcast

@Controller
public class UserController {

    // 普通参数
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name){
        System.out.printlnj("普通参数传递 name ==> " + name);
        return "{'module':'common param'}";
    }
}

http://localhost/commonParam?name=itcast&age=15

@Controller
public class UserController {

    // 普通参数
    @RequestMapping("/commonParam")
    @ResponseBody
    public String commonParam(String name, int age){
        System.out.printlnj("普通参数传递 name ==> " + name);
        System.out.printlnj("普通参数传递 age ==> " + age);
        return "{'module':'common param'}";
    }
}

Post请求

http://localhost/commmonParam

x-www-form-urlencoded

| key  | value  |
| ---- | ------ |
| name | itcast |
| age  | 15     |

form-data 可传文件

Post请求中文乱码处理

public class ServletContainersInitConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigclasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigclasses() {
        return new class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }

    // 乱码处理
    @Override
    protected Filter[] getServletFilters() {
        CharacterEncodingFilter filter = new CharacterEncodingFilter();
        filter.setEncoding("UTF-8");
        return new Filter[]{filter};
        // return new Filter[]{filter1, filter2};
    }
}

5种类型参数传递

普通参数

//普通参数:请求参数名与形参名不同
@RequestMapping("/commonParamDifferentName")
@ResponseBody
public String commonParamDifferentName(@RequestParam("name") String userName, int age){ // 绑定请求参数与形参
    System.out.println("普通参数传递 userName ==> " + userName);
    System.out.println("普通参数传递 age ==> " + age);
    return "{'module':'common param different name'}";
}

POJO类型参数

public class User {
    private String name;
    private int age;
}
//P0J0参数
@RequestMapping("/pojoParam")
@ResponseBody
public String pojoParam(User user){
    System.out.println("pojo参数传递 user ==> "+user);
    return "{'module':'pojo param'}";
}

嵌套POJO类型参数

public class Address {
    private String province;
    private String city;

}
public class User {
    private String name;
    private int age;

    private Address address;
}
//嵌套POJO参数
@RequestMapping("/pojoContainPojoParam")
@ResponseBody
public String pojoContainPojoParam(User user){
    System.out.println("pojo嵌套pojo参数传递 user ==> " + user );
    return "{'module':'pojo contain pojo param'}";
}

数组类型参数

//数组参数
@RequestMapping("/arrayParam")
@ResponseBody
public String arrayParam(String[] likes){
    System.out.println("数组参数传递 likes ==> "+ Arrays.toString(likes));
    return "{'module':'array param'}";
}

集合类型参数

//集合參数
@RequestMapping("/listParam")
@ResponseBody
public String listParam(@RequestParam List<String> likes){
    System.out.println("集合参数传递 likes ==> "+ likes);
    return "{'module':'1ist param'}";
}

json数据传递参数

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.9.0</version>
</dependency>
@Configuration
@ComponentScan("com.xxx.controller")
@EnableWebMvc  // 开启json数据转对象的功能
public class SpringMvcConfig {

}

json数组

//集合参数:json格式
@RequestMapping("/listParamForJson")
@ResponseBody
public String listParamForJson(@RequestBody List<String> likes){
    System.out.println("list common(json) 参数传递 list ==> "+likes);
    return "{'module':'list common for json param'}";
}

json对象(POJO)

//POJO 参数:json格式
@RequestMapping("/pojoParamForJson")
@ResponseBody
public String pojoParamForJson(@RequestBody User user){
    System.out.println("pojo(json)参数传递 user ==>"+user);
    return "{'module':'pojo for json param'}";
}

json数组(POJO)

//集合参数:json格式
@RequestMapping("/listPojoParamForJson")
@ResponseBody
public String listPojoParamForJson(@RequestBody List<User> list){
    System.out.println("list pojo(json) 参数传递 list ==> "+list);
    return "{'module': 'list pojo for json param'}";
}



日期类型参数传递

  • 日期类型数据基于系统不同格式也不尽相同
    • 2088-08-18
    • 2088/08/18
    • 08/18/2088
//日期参数
@RequestMapping("/dataParam")
@ResponseBody
public String dataParam(Date date,
                        @DateTimeFormat(pattern="yyyy-MM-dd") Date date1,
                        @DateTimeFormat(pattern="yyyy/NM/dd HH:mm:ss") Date date2){
    System.out.println("参数传递 date ==> "+date);
    System.out.println("参数传递 date1(yyyy-MM-dd) ==> "+date1);
    System.out.println("参数传递 date2(yyyy/MM/dd HH:mm:ss) ==> "+date2);
    return "{'module':'data param'}";
}

响应

  • 响应页面
  • 响应数据
    • 文本数据
    • json数据
@Controller
public class UserController {

    //响应页面/跳转页面
    @RequestMapping("/toJumpPage")
    public String toJumpPage(){
        System.out.println("跳转页面");
        return "page.jsp";
    }

    //响应文本数据
    @RequestMapping("/toText")
    @ResponseBody
    public String toText(){
        System.out.println("返回纯文本数据");
        return "response text";
    }

    //响应POJO对象
    @RequestMapping("/toJsonPOJO")
    @ResponseBody
    public User toJsonPOJO(){
        System.out.println("返回json对象数据");
        User user = new User();
        user.setName("itcast");
        user.setAge(15);
        return user;
    }

    //响应POJO集合对象
    @RequestMapping("/toJsonList")
    @ResponseBody
    public List<User> toJsonList(){
        System.out.println("返回json集合数据");
        User user1 = new User();
        user1.setName("传智播客");
        user1.setAge(15);

        User user2 = new User();
        user2.setName("黑马程序员");
        user2.setAge(12);

        List<User> userList = new ArrayList<~>();
        userList.add(user1);
        userList.add(user2);

        return userList;
    }
}

alt text alt text alt text alt text


alt text alt text alt text

REST风格

  • REST(Representational State Transfer), 表现形式状态转换

    • 传统风格资源描述形式

      http://localhost/user/getById?id=1
      http://localhost/user/saveUser

    • REST风格描述形式

      http://localhost/user/1
      http://localhost/user

  • 优点:

    • 隐藏资源的访问行为,无法通过地址得知对资源是何种操作
    • 书写简化
  • 按照REST风格访问资源时使用行为动作区分对资源进行了何种操作

    • http://localhost/users 查询全部用户信息 GET(查询)
    • http://localhost/users/1 查询指定用户信息 GET(查询)
    • http://localhost/users 添加用户信息 POST(新增)
    • http://localhost/users 修改用户信息 PUT(修改/更新)
    • http://localhost/users/1 删除用户信息 DELETE(删除)
  • 根据REST风格对资源进行访问称为RESTful

上述行为是约定方式,约定不是规范,可以打破,所以称REST风格,而不是REST规范 描述模块的名称通常使用复数,也就是加s的格式描述,表示此类资源,而非单个资源,例如:users、 boaks、 accounts……

REST入门案例

@Controller
public class UserController {
    @RequestMapping(value = "/users", method = RequestMethod.POST)
    @ResponseBody
    public String save(){
        System.out.println("user save.. .");
        return "{'module':'user save '}";
    }

    @RequestMapping(value = "/users/{id}", method = RequestMethod.DELETE)
    @ResponseBody
    public String delete(@PathVariable Integer id){
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }

    @RequestMapping(value = "/users", method = RequestMethod.PUT)
    @ResponseBody
    public String update(@RequestBody User user){
        System.out.println("user update..."+user);
        return "{'module':'user update'}";
    }

    @RequestMapping(value = "/users/{id}", method = RequestMethod.GET)
    @ResponseBody
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }

    @RequestMapping(value = "/users", method = RequestMethod.GET)
    @ResponseBody
    public String getAl1(){
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }



RESTful快速开发

//@Controller
//@RequestBody
@RestController
@Requestmapping("/books")
public class UserController {

    //@RequestMapping(method = RequestMethod.POST)
    @PostMapping
    public String save(){
        System.out.println("user save.. .");
        return "{'module':'user save '}";
    }

    //@RequestMapping(value = "/{id}", method = RequestMethod.DELETE)
    @DeleteMapping("/{id}")
    public String delete(@PathVariable Integer id){
        System.out.println("user delete..." + id);
        return "{'module':'user delete'}";
    }

    //@RequestMapping(method = RequestMethod.PUT)
    @PutMapping
    public String update(@RequestBody User user){
        System.out.println("user update..."+user);
        return "{'module':'user update'}";
    }

    //@RequestMapping(value = "/{id}", method = RequestMethod.GET)
    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id){
        System.out.println("user getById..."+id);
        return "{'module':'user getById'}";
    }

    //@RequestMapping(method = RequestMethod.GET)
    @GetMapping
    public String getAll(){
        System.out.println("user getAll...");
        return "{'module':'user getAll'}";
    }
}

基于RESTful页面数据交互(后台接口开发)

@RestController
@RequestMapping("/books")
public class BookController {


    @PostMapping
    public String save(@RequestBody Book book){
        System.out.println("book save ==> " + book);
        return "{'module':'book save success'}"
        return null;
    }

    @GetMapping
    public List<Book> getAll(){
        List<Book> bookList = new ArrayList<Book>();

        Book book1 = new Book();
        book1.setType("计算机");
        book1.setName("SpringMVc入门教程");
        book1.setDescription("小试牛刀");
        bookList.add(book1);

        Book book2 = new Book();
        book2.setType("计算机");
        book2.setName("SpringMvc实战教程");
        book2.setDescription("一代宗师");
        bookList.add(book2);

        return bookList;
    }
}

基于RESTful页面数据交互(页面访问处理)

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {

    @Override
    protected void addResourcehandlers(resourceHandlerRegistry registry) {
        //当访问/pages/????时,走/pages目录下的内容
        registry.addResourceHandler("/pages/**").addResourceLocations("/pages/");
        registry.addResourceHandler("/js/**").addResourceLocations("/js/");
        registry.addResourceHandler("/css/**").addResourceLocations("/css/");
        registry.addResourceHandler("/plugins/**").addResourceLocations("/plugins/");
    }
}


  • 先做后台功能,开发接口并调通接口
  • 再做页面异步调用,确认功能可以正常访问
  • 最后完成页面数据展示
  • 补充:放行静态资源访问

SSM整合(整合配置)

  1. 创建工程

  2. SSM整合

    • Spring
      • SpringConfig
    • MyBatis
      • MybatisConfig
      • JdbcConfig
      • jdbc.properties
    • SpringMVC
      • ServletConfig
      • SpringMvcConfig
  3. 功能模块

    • 表与实体类
    • dao(接口+自动代理)
    • service(接口+实现类)
      • 业务层接口测试(整合JUnit)
    • controller
      • 表现层接口测试(PostMan)

创建工程

Project Structure --> Modules --> + --> Maven --> New Module --> Maven --> Archetype --> webapp Project Structure --> New Folder (java, resources, test.java)

org.springframework:spring-webmvc:5.2.10.RELEASE
org.springframework:spring-jdbc:5.2.10.RELEASE
org.springframework:spring-test:5.2.10.RELEASE
org.mybatis:mybatis:3.5.6
org.mybatis:mybatis-spring:1.3.0
mysql:mysql-connector-java:5.1.47
com.alibaba:druid:1.1.16
junit junit4.12 (test)
javax.servlet.javax.servlet-api:3.1.0 (provided)
com.fasterxml.jackson.core:jackson-databind:2.9.0
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.1</version>
            <configuration>
                <port>80</port>
                <path>/</path>
            </configuration>
        </plugin>
    </plugins>
</build>

src/main/java/ 下创建目录 com.xxx.configcom.xxx.servicecom.xxx.daocom.xxx.controllercom.xxx.domaincom.xxx.service 下创建目录 impl

SpringConfig.java

package com.xxx.config;

@Configuration
@Component({"com.xxx..service"})
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class})
public class SpringConfig {

}

src/main/resources 下创建 jdbc.properties jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_db
jdbc.username=root
jdbc.password=root

JdbcConfig.java

package com.xxx.config;

public class JdbcConfig {

    @Value("${jdbc.drive}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }
}

MyBatisConfig.java

package com.xxx.config;

public class MyBatisConfig {

    @Bean
    public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setTypeAliasesPackage("com.xxx.damain");
        return factoryBean;
    }

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer msc = new MapperScannerConfigurer();
        msc.setBasePackage("com.xxx.dao");
        return msc;
    }
}

ServletConfig.java

package com.xxx.config;

public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigClasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}

SpringMvcConfig.java

package com.xxx.config;

@Configuration
@ComponentScan("com.xxx.controller")
@EnableWebMvc
public class SpringMvcConfig {

}

SSM整合(功能模块发开)

Book.java

public class Book {
    private Integer id;
    private String type;
    private String name;
    private String description;

    public Integer getId() {
        return id;
    }

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

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }
}

BookDao.java

package com.xxx.dao;

public interface BookDao {

    //@Insert("insert into tbl_book values(null, #{type}, #{name}, #{description})")
    @Insert("insert into tbl_book (type,name,description) values(null, #{type}, #{name}, #{description})")
    public void save(Book book);

    @Update("update tbl_book set type= #{type}, name= #{name}, description= #{description} where id = #{id}")
    public void update(Book book);

    @Delete("delete from tbl_book where id = #{id}")
    public void delete(Integer id);

    @Select("select * from tbl_book where id = #{id}")
    public Book getById(Integer id);

    @Select("select * from tbl_book")
    public List<Book> getAll();
}

BookService.java

package com.xxx.service;

public interface BookService {

    public boolean save(Book book);

    public boolean update(Book book);

    public boolean delete(Integer id);

    public Book getById(Integer id);

    public List<Book> getAll();
}

BookServiceImpl.java

package com.xxx.service.impl;

@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    public boolean save(Book book) {
        bookDao.save(book);
        return true;
    }

    public boolean update(Book book) {
        bookDao.update(book);
        return true;
    }

    public boolean delete(Integer id) {
        bookDao.delete(id);
        return true;
    }

    public Book getById(Integer id){
        return bookDao.getById(id);
    }

    public List<Book> getA11() {
        return bookDao.getAll();
    }
}

BookController.java

package com.xxx.controller;

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @PostMapping
    public boolean save(@RequestBody Book book) {
        return bookService.save(book);
    }

    @PutMapping
    public boolean update(@RequestBody Book book) {
        return bookService.update(book);
    }

    @DeleteMapping("/{id}")
    public boolean delete(@PathVariable Integer id) {
        return bookService.delete(id);
    }

    @GetMapping("/{id}")
    public Book getById(@PathVariable Integer id){
        return bookService.getById(id);
    }

    @GetMapping
    public List<Book> getA11() {
        return bookService.getAll();
    }
}

SSM整合(接口测试)

src/main/test 下创建 com.xxx.service.BookServiceTest

package com.xxx.service;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig. class)
public class BookServiceTest {

    @Autowired
    private BookService bookService;

    @Test
    public void testGetById(){
        Book book = bookService.getById(1);
        System.out.println(book);
    }

    @Test
    public void testGetAll(){
        List<Book> all = bookService.getAll();
        System.out.println(all);
    }
}

添加事务管理

SpringConfig.java

package com.xxx.config;

@Configuration
@Component({"com.xxx..service"})
@PropertySource("jdbc.properties")
@Import({JdbcConfig.class, MyBatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {

}

JdbcConfig.java

package com.xxx.config;

public class JdbcConfig {

    @Value("${jdbc.drive}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource){
        DataSourceTransactionManager ds = new DataSourceTransactionManager();
        ds.setDataSource(dataSource);
    }
}

BookService.java

package com.xxx.service;

@Transactionl
public interface BookService {

    public boolean save(Book book);

    public boolean update(Book book);

    public boolean delete(Integer id);

    public Book getById(Integer id);

    public List<Book> getAll();
}

表现层与前端数据传输协议定义

返回格式繁多 统一数据格式 无法辨认操作 指定code code表示操作成功或失败 操作成功再取数据 操作失败,用户得到什么信息?

表现层与前端数据传输协议实现

Result.java

package com.xxx.controller;

public class Result {
    private Object data;
    private Integer code;
    private String msg;

    public Result() {

    }

    public Result(Integer code, Object data) {
        this.data = data;
        this.code = code;
    }

    public Result(Integer code, Objiect data, String msg) {
        this.data = data;
        this.code = code;
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}

Code.java

package com.xxx.controller;

public class Code {
    public static final Integer SAVE_OK = 20011;
    public static final Integer DELETE_OK = 20021;
    public static final Integer UPDATE_OK = 20031;
    public static final Integer GET_OK = 20041;

    public static final Integer SAVE_ERR = 20010;
    public static final Integer DELETE_ERR = 20020;
    public static final Integer UPDATE_ERR = 20030;
    public static final Integer GET_ERR = 20040;
}

BookController.java

package com.xxx.controller;

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @PostMapping
    public Result save(@RequestBody Book book) {
        boolean flag = bookService.save(book);
        return new Result(flag ? Code.SAVE_OK : Code.SAVE_ERR, flag);
    }

    @PutMapping
    public Result update(@RequestBody Book book) {
        boolean flag = bookService.update(book);
        return new Result(flag ? Code.UPDATE_OK : Code.UPDATE_ERR, flag);
    }

    @DeleteMapping("/{id}")
    public Result delete(@PathVariable Integer id) {
        boolean flag =  bookService.delete(id);
        return new Result(flag ? Code.DELETE_OK : Code.DELETE_ERR, flag);
    }

    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id){
        Book book = bookService.getById(id);
        Integer code = book != null ? Code.GET_OK:Code.GET_ERR;
        String msg = book != null ? "" : "数据查询失败,请重试!";
        return new Result(code, book, msg);
    }

    @GetMapping
    public Result getA11() {
        List<Book> bookList = bookService.getAll();
        Integer code = bookList != null ? Code.GET_OK:Code.GET_ERR;
        String msg = bookList != null ? "" : "数据查询失败,请重试!";
        return new Result(code, bookList, msg);
    }
}

异常处理器

出现异常现象的常见位置与常见诱因如下:

  • 框架内部抛出的异常:因使用不合规导致
  • 数据层抛出的异常:因外部服务器故障导致(例如:服务器访问超时)
  • 业务层抛出的异常:因业务逻辑书写错误导致(例如:遍历业务书写操作,导致索引异常等)
  • 表现层抛出的异常:因数据收集、校验等规则导致(例如:不匹配的数据类型间导致异常)
  • 工具类抛出的异常:因工具类书写不严谨不够健壮导致(例如:必要释放的连接长期未释放等)

各个层级均出现异常,异常处理代码书写在哪一层?
>> 所有的异常均抛出到表现层进行处理

表现层处理异常,每个方法中单独书写,代码书写量巨大且意义不强,如何解决
>> AOP思想

  • 异常处理器
    • 集中的、统一的处理项目中出现的异常

ProjectExceptionAdvice.java

package com.xxx.controller;

@RestControllerAdvice
public class ProjectExceptionAdvice {
    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        return new Result(666, nul1);
    }
}

项目异常处理

项目异常分类

  • 业务异常 (BusinessException)
    • 规范的用户行为产生的异常
    • 不规范的用户行为操作产生的异常
  • 系统异常(SystemException)
    • 项目运行过程中可预计且无法避免的异常
  • 其他异常 (Exception)
    • 编程人员未预期到的异常

项目异常处理方案

  • 业务异常 (BusinessException)
    • 发送对应消息传递给用户,提醒规范操作
  • 系统异常(SystemException)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给运维人员,提醒维护
    • 记录日志
  • 其他异常 (Exception)
    • 发送固定消息传递给用户,安抚用户
    • 发送特定消息给编程人员,提醒维护(纳入预期范围内)
    • 记录日志

SystemException.java

package com.xxx.exception;

public class SystemException extends RuntimeException{
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public SystemException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public SystemException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }
}

BusinessException.java

package com.xxx.exception;

public class BusinessException extends RuntimeException{
    private Integer code;

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public SystemException(Integer code, String message) {
        super(message);
        this.code = code;
    }

    public SystemException(Integer code, String message, Throwable cause) {
        super(message, cause);
        this.code = code;
    }
}

BookServiceImpl.java

package com.xxx.service.impl;

public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    public boolean save(Book book){
        bookDao.save(book);
        return true;
    }

    public boolean update(Book book){
        bookDao.update(book);
        return true;
    }

    public boolean delete(Integer id){
        bookDao.delete(id);
        return true;

    }

    public Book getById(Integer id){
        if(id == 1){
            throw new BusinessException(Code.BUSINESS_ERR, "请不要使用你的技术挑战我的耐性!");
        }
        //将可能出现的异常进行包装,转换程自定义异常
        try{
            int i = 1/0;

        }catch(ArithmeticException e){
            throw new SystemException(Code.SYSTEM_TIMMEOUT_ERR, "服务器访问超时,请重试!", e);
        }

        return bookDao.getById(id);
    }

    public List<Book> getAll(){
        return bookDao.getAll();
    }

}

Code.java

package com.xxx.controller;

public class Code {
    public static final Integer SAVE_OK = 20011;
    public static final Integer DELETE_OK = 20021;
    public static final Integer UPDATE_OK = 20031;
    public static final Integer GET_OK = 20041;

    public static final Integer SAVE_ERR = 20010;
    public static final Integer DELETE_ERR = 20020;
    public static final Integer UPDATE_ERR = 20030;
    public static final Integer GET_ERR = 20040;

    public static final Integer SYSTEM_ERR = 50001;
    public static final Integer SYSTEM_TIMEOUT_ERR = 50002;
    public static final Integer SYSTEM_UNKNOW_ERR = 59999;

    public static final Integer BUSINESS_ERR = 60002;
}

ProjectExceptionAdvice.java

package com.xxx.controller;

@RestControllerAdvice
public class ProjectExceptionAdvice {
    @ExceptionHandler(SystemException.class)
    public Result doSystemException(SystemException ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员
        return new Result(ex.getCode(), null, ex.getMessage());
    }

    @ExceptionHandler(BusinessException.class)
    public Result doBusinessException(BusinessException ex){
        return new Result(ex.getCode(), null, ex.getMessage());
    }

    @ExceptionHandler(Exception.class)
    public Result doException(Exception ex){
        //记录日志
        //发送消息给运维
        //发送邮件给开发人员
        return new Result(Code.SYSTEM_UNKNOW_ERR, null, "系统繁忙,请稍后再试!");
    }
}

拦截器简介

拦截器概念

  • 拦截器(Interceptor)是一种动态拦截方法调用的机制,在SpringMvc中动态拦截控制器方法的执行
  • 作用:
    • 在指定的方法调用前后执行预先设定的代码
    • 阻止原始方法的执行

拦截器与过滤器区别

  • 归属不同:Filter属于Servlet技术,Interceptor属于SpringMvc技术
  • 拦截内容不同:Filter对所有访问进行增强,Interceptor仅针对SpringMvc的访问进行增强

拦截器入门案例

制作拦截器功能类

配置拦截器的执行位置

com.xxx.controller 下创建 interceptor 目录

package com.xxx.controller.interceptor;

@Component
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override
    public boolean afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

SpringMvcSupport.java

package com.xxx.config;

@Configuration
public class SpringMvcSupport extends WebMvcConfigurationSupport {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    protected void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandlers("/pages/**").addResourceLocations("/pages/");
    }

    @Override
    protected void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptors(projectInterceptor).addPathPatterns("/books");
    }
}

SpringMvcConfig.java

package com.xxx.config;

@Configuration
@ComponentScan({"com.xxx.controller", "com.xxx.config"})
@EnableWebMvc
public class SpringMvcConfig {

}

SpringMvcConfig.java

@Configuration
@Component({"com.xxx.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptors(projectInterceptor).addPathPatterns("/books", "/books/*");
    }
}


拦截器参数

package com.xxx.controller.interceptor;

@Component
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String contentType = request.getHeader("Content-Type");
        HandlerMethod hm = (HandlerMethod)handler;
        System.out.println("preHandle..."+contentType);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override
    public boolean afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("afterCompletion...");
    }
}

拦截器链配置

多拦截器执行顺序

  • 当配置多个拦截器时,形成拦截器链
  • 拦截器的运行顺序参照拦截器添加顺序为准
  • 当拦截器中出现对原始处理器的拦截,后面的拦截器均终止运行
  • 当拦截器运行中断,仅运行配置在前面的拦截器的aftercompletion操作
package com.xxx.controller.interceptor;

@Component
public class ProjectInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String contentType = request.getHeader("Content-Type");
        HandlerMethod hm = (HandlerMethod)handler;
        System.out.println("preHandle..."+contentType);
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override
    public boolean afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("afterCompletion...");
    }
}
package com.xxx.controller.interceptor;

@Component
public class ProjectInterceptor2 implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle...222");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...222");
    }

    @Override
    public boolean afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

        System.out.println("afterCompletion...222");
    }
}
@Configuration
@Component({"com.xxx.controller"})
@EnableWebMvc
public class SpringMvcConfig implements WebMvcConfigurer {
    @Autowired
    private ProjectInterceptor projectInterceptor;

    @Autowired
    private ProjectInterceptor2 projectInterceptor2;


    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptors(projectInterceptor).addPathPatterns("/books", "/books/*");
        registry.addInterceptors(projectInterceptor2).addPathPatterns("/books", "/books/*");
    }
}

Spring Boot 简介

Spring Boot 概述

  • Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化Spring应用的初始搭建以及开发过程

  • Spring程序缺点

    • 配置繁琐
    • 依赖设置繁琐
  • SpringBoot程序优点

    • 自动配置
    • 起步依赖(简化依赖配置)
    • 辅助功能(内置服务器,……)

Spring Boot 起步依赖

  • starter

    • SpringBoot中常见项目名称,定义了当前项目使用的所有项目坐标,以达到减少依赖配置的目的
  • parent

    • 所有SpringBoot项目要继承的项目,定义了若干个坐标版本号(依赖管理,而非依赖),以达到减少依赖冲突的目的
    • spring-boot-starter-parent(2.5.0)与 spring-boot-starter-parent(2.4.6)共计57处坐标版本不同
  • 实际开发

    • 使用任意坐标时,仅书写GAV中的G和A,V由SpringBoot提供
    • 如发生坐标错误,再指定version(要小心版本冲突)
  • 辅助功能

Spring Boot 程序启动

  • 启动方式

  • SpringBoot在创建项目时,采用jar的打包方式

  • SpringBoot的引导类是项目的入口,运行main方法就可以启动项目

  • 使用maven依赖管理变更起步依赖项目

  • Jetty比Tomcat更轻量级,可扩展性更强(相较于Tomcat),谷歌应用引擎(GAE)已经全面切换为Jetty

Spring Boot入门案例

  • 原生开发SpringMVC程序过程
<dependencies>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
</dependencies>
public class ServletConfig extends AbstractAnnotationConfigDispatcherServletInitializer {
    protected Class<?>[] getRootConfigclasses() {
        return new Class[]{SpringConfig.class};
    }

    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{SpringMvcConfig.class};
    }

    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
@Configuration
@ComponentScan("com.xxx.controller")
@EnableWebMvc
public class SpringMvcConfig {

}
@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    @GetMapping("/{id}")
    public Result getById(@PathVariable Integer id) {
        Book book = bookService.getById(id);
        Integer code = book != null ? Code.GET_OK : Code.GET_ERR;
        String msg = book != null ? "数据查询失败,请重试!";
        return new Result(code, book, msg);
    }
}
  • 创建Boot项目

Project Structure --> New Module --> Spring Initializr --> 填写信息 --> 勾选(Spring Web)--> Finish 删除除 pom.xmlsrc/ 以外的文件

BookController.java

@RestController
@RequestMapping("/books")
public class BookController {

    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id) {
        System.out.println("id ==> "+id);
        return "hello, spring boot!";
    }
}

Spring Boot工程官方创建方式

  • 最简SpringBoot程序所包含的基础文件
    • pom.xml文件
    • Application类

pom.xml

<?xml version="1.0" encoding="UTF -8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.0</version>
    </parent>
    <groupId>com.xxx</groupId>
    <artifactId>springboot-01-quickstart</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

Application

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

官方创建 --> links

Spring Boot快速启动

配置文件格式

resources 下创建 application.yml

application.yml

server:
  port: 80

设置为工程的配置文件

Project Settings --> Facets --> Customize Spring Boot

SpringBoot配置文件加载顺序(了解)

application.properties > application.yml application.yaml

yaml格式

  • YAML(YAML Ain't Markup Language),一种数据序列化格式

  • 优点:

    • 容易阅读
    • 容易与脚本语言交互
    • 以数据为核心,重数据轻格式
  • YAML文件扩展名

    • .yml(主流)
    • .yaml

yaml 语法规则

  • 大小写敏感

  • 属性层级关系使用多行描述,每行结尾使用冒号结束

  • 使用缩进表示层级关系,同层级左侧对齐,只允许使用空格(不允许使用Tab键)

  • 属性值前面添加空格(属性名与属性值之间使用冒号+空格作为分隔)

  • # 表示注释

  • 核心规则:数据前面要加空格与冒号隔开

  • 数组数据在数据书写位置的下方使用减号作为数据开始符号,每行书写一个数据,减号与数据间空格分隔

yaml数据读取方式

application.yaml

lesson: SpringBoot

server:
  port: 80

enterprise:
  name: itcast
  age: 16
  tel: 40061840000
  subject:
    - Java
    - 前端
    - 大数据

BookController.java

package com.xxx.controller;

@RestController
@RequestMapping("/books")
public class BookController {
    @Value("${lesson}")
    private String lesson;

    @Value("${server.port}")
    private Integer port;

    @Value("${enterprise.subject[0]}")
    private String subject_00;

    @Autowired
    private Environment environment;

    @Autowired
    private Enterprise enterprise;

    @GetMapping("/{id}")
    public String getById(@PathVariable Integer id) {
        System.out.println(lesson);
        System.out.println(port);
        System.out.println(subject_00);
        System.out.println("-----------");
        System.out.println(environment.getProperty("lesson"));
        System.out.println(environment.getProperty("server.port"));
        System.out.println(environment.getProperty("server.age"));
        System.out.println(environment.getProperty("server.subject[1]"));
        System.out.println("-----------");
        System.out.println(enterprise);
        return "hello, spring boot!";
    }
}

Enterprise.java

@Component
@ConfigurationProperties(prefix = "enterprise")
public class Enterprise {
    private String name;
    private Integer age;
    private String tel;
    private String[] subject;

    // ...
}

自定义对象封装数据警告解决方案

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-configuration-processor</artifactId>
    <optional>true</optional>
</dependency>

多环境开发配置

yaml 格式书写

application.yml

# 设置启用的环境
spring:
  profiles:
    active: dev
---
# 开发
spring: # 四行写法,等同于下面两行写法
  config:
    activate:
      on-profile: dev
server:
  port: 80
---
# 生产
spring:
  profiles: pro
server:
  port: 81
---
# 测试
spring:
  profiles: test
server:
  port: 82
---

properties 格式书写

application.properties

# 设置启用的环境
spring.profiles.active=dev

application-dev.properties

server.port=8080

application-pro.properties

server.port=8081

启动命令格式

java -jar springboot.jar --spring.profiles.active=test
java -jar springboot.jar --serer.port=88
java -jar springboot.jar --serer.port=88 --spring.profiles.active=test

打包失败常见问题:

  1. 打包前使用maven clean

  2. 编码问题

    Settings --> File Encodings -->

兼容问题(Maven与boot)

配置文件分类

整合JUnit

public interface BookService {
    public void save();
}
@Service
public class BookServiceImpl implements BookService {
    @Override
    public void save() { System.out.println("book service is running ..."); }
}
@SpringBootTest
class SpringbootTestApplicationTests {
    @Autowired
    private BookService bookService;

    @Test
    public void save() {
        bookServie.save();
    }
}

整合MyBatis

Spring整合MyBatis:

  • SpringConfig
    • 导入JdbcConfig
    • 导入MyBatisConfig
  • JDBCConfig
    • 定义数据源(加载properties配置项:driver、url、username、password)
  • MyBatisConfig
    • 定义SqlSessionFactoryBean
    • 定义映射配置

Spring Boot整合:

创建项目

New Module --> Spring Initializr --> Next --> SQL --> MyBatis Framework、MySQL Driver

package com.xxx.domain;

public class Book {
    private Integer id;
    private String name;
    private String type;
    private String description;

    // ... getter setter toString
}
package com.xxx.dao;


@Mapper
public interface BookDao {

    @Select("select * from tbl_boo where id = #{id}")
    public Book getById(Integer id);
}

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db
    username: root
    password: root

SpringbootApplicationTests.java

class SpringbootApplicationTests {

    @Autowired
    private BookDao bookDao; // 解决报错,Inspections --> Severity (Error --> Warning)

    @Test
    void testGetById() {
        Book book = bookDao.getById(1);
        System.out.println(book);
    }
}

使用druid数据源

pom.xml

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
</dependency>

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    username: root
    password: root
    type: com.alibba.druid.pool.DruidDataDource

整合SSM案例

  1. pom.xml
    配置起步依赖,必要的资源坐标 (druid)
  2. application.yml
    设置数据源、端口等
  3. 配置类
    全部删除
  4. dao
    设置@Mapper
  5. 测试类
  6. 页面
    放置在resources目录下的static目录中

分模块开发的意义

  • 将原始模块接照功能拆分成若干个子模块,方便模块间的相互调用,接口共享

按功能划分模块

分模块开发与设计

依赖传递

  • 依赖指当前项目运行所需的jar,一个项目可以设置多个依赖

  • 格式:

    <!--设置当前项目所依赖的所有jar-->
    <dependencies>
    <!--设置具体的依赖-->
        <dependency>
            <!--依赖所属群组id-->
            <groupId>org.springframework</groupId>
            <!--依赖所属项目id-->
            <artifactId>spring-webmvc</artifactId>
            <!--依赖版本号-->
            <version>5.2.10.RELEASE</version>
        </dependency›
    </dependencies>
    
  • 依赖具有传递性

    • 直接依赖:在当前项目中通过依赖配置建立的依赖关系
    • 间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源

  • 依赖传递冲突问题

    • 路径优先:当依赖中出现相同的资源时,层级越深,优先级越低,层级越浅,优先级越高
    • 声明优先:当资源在相同层级被依赖时,配置顺序靠前的覆盖配置顺序靠后的
    • 特殊优先:当同级配置了相同资源的不同版本,后配置的覆盖先配置的
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.11</version>
        <scope>test</scope>
    </dependency>
    

    使用 4.11 版本

可选依赖与排除依赖

可选依赖

可选依赖指对外隐藏当前所依赖的资源——不透明

<dependency>
    <groupId>com.itheima</groupId>
    <artifactId>maven_03_pojo</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--可选依赖是隐藏当前工程所依赖的资源,隐藏后对应资源將不具有依赖传递性-->
    <optional>true</optional>
</dependency>

true 为对外隐藏依赖,false 为不对外隐藏依赖

排除依赖

排除依赖指主动断开依赖的资源,被排除的资源无需指定版本——不需要

<dependency>
    <groupId>com.itheima</groupId>
    <artifactId>maven_04_dao</artifactId>
    <version>1.0-SNAPSHOT</version>
    <!--排除依赖是隐藏当前资源对应的依赖关系-->
    <exclusions>
        <exclusion>
            <groupId>1og4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
        </exclusion>
    </exclusions〉
</dependency>

排除依赖资源仅指定GA即可,无需指定V

聚合

  • 聚合:将多个模块组织成一个整体,同时进行项目构建的过程称为聚合
  • 聚合工程:通常是一个不具有业务功能的“空”工程(有且仅有一个pom文件)
  • 作用:使用聚合工程可以将多个工程编组,通过对聚合工程进行构建,实现对所包含的模块进行同步构建
    • 当工程中某个模块发生更新(变更)时,必须保障工程中与已更新模块关联的模块同步更新,此时可以使用聚合工程来解决批量模块同步构建的问题
<project>
    <groupId>host.guoguo</groupId>
    <artifactId>maven_01_parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging> <!--打包方式为pom-->

    <!--设置管理模块的名称-->
    <modules>
        <module>../maven_02_ssm</module>
        <module>../maven_03_pojo</module>
        <module>../maven_02_dao</module>
    </modules>
</project>

继承

  • 概念:继承描述的是两个工程间的关系,与java中的继承相似,子工程可以继承父工程中的配置信息,常见于依赖关系的继承
  • 作用:
    • 简化配置
    • 减少版本冲突
<!--配置当前工程继承自parent工程-->
<project>
    <parent>
        <groupId>host.guoguo</groupId>
        <artifactId>maven_01_parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../maven_01_parent/pom.xml</relativePath>
    </parent>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>


parent工程

<project>
    <dependencied>
    ...
    </dependencied>
    <!--定义依赖管理-->
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
                <scope>test</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>
</project>

聚合与继承的区别

  • 作用
    • 聚合用于快速构建项目
    • 继承用于快速酿置
  • 相同点:
    • 聚合与继承的pom.xml文件打包方式均为pom,可以将两种关系制作到同一个pom文件中
    • 聚合与继承均属于设计型模块,并无实际的模块内容
  • 不同点:
    • 聚合是在当前模块中配置关系,聚合可以感知到参与聚合的模块有哪些
    • 继承是在子模块中配置关系,父模块无法感知哪些子模块继承了自己

属性

<parent>
    <dependencies>
        <dependency> 
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>${spring.version}</version>
        </dependency>
    </dependencies>

    <!--定义属性-->
    <properties>
        <spring.version>5.2.10.RELEASE</spring.version>
    </properties>
</parent>

其他属性

  • 属性列表
  1. 自定义属性(常用)
  2. 内置属性
  3. Setting属性
  4. Java系统属性
  5. 环境变量属性

查看属性

mvn help:system

配置文件加载属性

maven_01_parent|--|pom.xml

<parent>
    <!--定义属性-->
    <properties>
        <spring.version>5.2.10.RELEASE</spring.version>
        <jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url>
    </properties>

    <build>
        <resources>
            <resource>
                <!--<directory>../maven_02_ssm/src/main/resources</directory>-->
                <directory>${project.basedir}/src/main/resources</directory>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</parent>

maven_02_ssm|--|jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=${jdbc.url}
jdbc.username=root
jdbc.password=root

maven_02_ssm|--|pom.xml

<parent>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.1</version>
                <configuration>
                    <port>80</port>
                    <path>/</path>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-war-plugin</artifactId>
                <version>3.2.3</version>
                <configuration>
                    <failOnMissingWebXml>false</failOnMissingWebXml>
                </configuration>
            </plugin>
        </plugins>
    </build>
</parent>

版本管理

  • 工程版本:
    • SNAPSHOT(快照版本)
      • 项目开发过程中临时输出的版本,称为快照版本
      • 快照版本会随着开发的进展不断更新
    • RELEASE(发布版本)
      • 项目开发到进入阶段里程碑后,向团队外部发布较为稳定的版本,这种版本所对应的构件文件是稳定的,即便进行功能的后续开发,也不会改变当前发布版本内容,这种版本称为发布版本
    • 发布版本
      • alpha版
      • beta版
      • 纯数字版

多环境开发

  • maven提供配置多种环境的设定,帮助开发者使用过程中快速切换环境
<parent>
    <!--配置多环境-->
    <profiles>
        <!--开发环境-->
        <profile>
            <id>env_dep</id>
            <properties>
               <jdbc.url>jdbc:mysql://127.0.0.1:3306/ssm_db</jdbc.url>
            </properties>
            <!--设定是否为启动的环境-->
            <activation>
                <activeByDefault>true</activeByDefault>
            </activation>
        </profile>
        <!--生产环境-->
        <profile>
            <id>env_pro</id>
            <properties>
               <jdbc.url>jdbc:mysql://61.41.134.129:3306/ssm_db</jdbc.url>
            </properties>
        </profile>
        <!--测试环境-->
        <profile>
            <id>env_test</id>
            <properties>
               <jdbc.url>jdbc:mysql://65.13.40.251:3306/ssm_db</jdbc.url>
            </properties>
        </profile>
    </profiles>
</parent>

跳过测试

  • 应用场景
    • 功能更新中并且没有开发完毕
    • 快速打包
<parent>
    <build>
        <plugins>
            <artifactId>maven-surefire-plugin</artifactId>
            <version>2.12.4</version>
            <configuration>
                <skipTests>false</skipTests>
                <!--排除掉不参与测试的内容-->
                <excludes>
                    <exclude>**/BookServiceTest.java</exclude>
                </excludes>
            </configuration>
        </plugins>
    </build>
</parent>

私服简介与安装

  • 私服是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题

  • Nexus

    • Sonatype公司的一款maven私服产品
    • 下载地址:https://help.sonatype.com/repomanager3/download

私服仓库分类

私服资源操作流程

私服仓库分类

私服上传与下载

资源上传与下载

maven|--|settings.xml

<settings>
    <servers>
        <server>
            <id>私服中的服务器id名称</id>
            <username>admin</username>
            <password>admin</password>
        </server>
    </servers>
    <mirrors>
        <!--私服的访问路径-->
        <mirror>
            <id>mirrorId</id>
            <mirrorOf>repositoryId</mirrorOf>
            <name>Human Readable Name for this Mirror.</name>
            <url>http://my.repository.com/repo/path</url>
        </mirror>
    </mirrors>
</settings>
<project>
    <!--配置当前工程保存在私服中的具体位置-->
    <distributionManagement>
        <repository>
            <id>repositoryId</id>
            <url>http://localhost:8081/repository/maven-release</url>
        </repository>
        <snapshotRepository>
            <id>repositoryId</id>
            <url>http://localhost:8081/repository/maven-snapshot</url>
        </snapshotRepository>
    </distributionManagement>
</project>

MyBatisPlus 简介

MyBatisPlus特性

  • 无侵入:只做增强不做改变,不会对现有工程产生影响
  • 强大的 CRUD 操作:内置通用 Mapper,少量配置即可实现单表CRUD 操作
  • 支持 Lambda:编写查询条件无需担心字段写错
  • 支持主键自动生成
  • 内置分页插件

MyBatis Plus 入门案例

  • 开发方式

    • 基于MyBatis使用MyBatisPlus
    • 基于Spring使用MyBatisPlus
    • 基于SpringBoot使用MyBatisPlus
  • SpringBoot整合MyBatis开发过程(复习)

    • 创建SpringBoot工程
    • 勾选配置使用的技术
    • 设置dataSource相关属性(JDBC参数)
    • 定义数据层接口映射配置

pom.xml

<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.4.1</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.16</version>
</dependency>

application.yml

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC
    username: root
    password: root
    type: com.alibba.druid.pool.DruidDataDource
@Mapper
public interface UserDao extends BaseMapper<User> {

}

标准CRUD操作

分页功能

package com.xxx.config;

@Configuration
public class MpConfig {

    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        // 1.定义mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        // 2.添加具体的拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        return mpInterceptor;
    }
}
@SpringBootApplication
public class MybatisplusApplication {

}
spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/ssm_db?serverTimezone=UTC
    username: root
    password: root
    type: com.alibba.druid.pool.DruidDataDource

# 开启mp的日志(输出到控制台)
mybatis-plus:
  configuration:
    log-impl: org.apache.itbatis.logging.stdout.StdOutImpl

条件查询方式

  • MyBatisPlus 将书写复杂的SQL查询条件进行了封装,使用编程的形式完成查询条件的组合
@SpringBootTest
class MybatisplusApplicationTests {

    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll() {
        // 方式一:按条件查询
        QueryWrapper qw = new QueryWrapper();
        qw.lt("age", 18);
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);

        // 方式二:lambda格式按条件查询
        QueryWrapper<User> qw = new QueryWrapper<User>();
        qw.lambda().lt(User::getAge, 18);
        List<User> userList = userDao.selectList(qw);
        System.out.println(userList);

        // 方式三:lambda格式按条件查询
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        lqw.lt(User::getAge, 10);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);

        // 方式三:lambda格式按条件查询
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        //lqw.lt(User::getAge, 30)
        //lqw.gt(User::getAge, 10);
        lqw.lt(User::getAge, 30).gt(User::getAge, 10);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);

        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        // 10到30岁之间
        lqw.lt(User::getAge, 30).gt(User::getAge, 10);
        // 小于10岁或者大于30岁
        lqw.lt(User::getAge, 10).or().gt(User::getAge, 30);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);
    }
}


null值处理

@Data
public class User {
    private Long id;
    private String name;
    private String password;
    private Integer age;
    private String tel;
}
package com.xxx.domain.query;

@Data
public class UserQuery extends User {
    private Integer age2; // 对可能会有上下限的属性做此操作
}
@SpringBootTest
class MybatisplusApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll() {
        // 模拟页面传递过来的查询数据
        UserQuery uq = new UserQuery();
        uq.setAge(10);
        uq.setAge2(30);

        // null判定
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        lqw.lt(null != userQuery.getAge2(), User::getAge, uq.getAge2());
        lqw.gt(null != userQuery.getAge(), User::getAge, uq.getAge());
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);

    }
}

查询投影

@SpringBootTest
class MybatisplusApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll() {

        // 查询投影
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        lqw.select(User::getId, User::getName, User::getAge);

        QueryWrapper<User> lqw = new QueryWrapper<User>();
        lqw.select("id", "name", "age", "tel")

        QueryWrapper<User> lqw = new QueryWrapper<User>();
        // lqw.select("count(*)")
        lqw.select("count(*) as count")

        lqw.groupBy("tel")
        List<Map<String, Object>> userList = userDao.selectMaps(lqw);
        System.out.println(userList);

    }
}

查询条件设定

查询条件

  • 范围匹配(>、=、between)
  • 模糊匹配(like)
  • 空判定 (null)
  • 包含性匹配(in)
  • 分组 (group)
  • 排序 (order)
@SpringBootTest
class MybatisplusApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testGetAll() {

        // 查询投影
        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        // 等同于=
        lqw.eq(User::getName, "Jerry").eq(User::getPassword, "jerry");
        User loginUser = userDao.selectOne(lqw);
        System.out.println(loginUser);

        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        // 范围查询 lt le gt ge eq between < <= > >=
        lqw.between(User::getAge, 10, 30);
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);

        LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<user>();
        // 模糊匹配 like
        lqw.like(User::getName, "J");
        //lqw.likeLeft(User::getName, "J");  %J
        List<User> userList = userDao.selectList(lqw);
        System.out.println(userList);


    }
}

更多查询条件设置参看https://mybatis.plus/guide/wrapper.html#abstractwrapper

字段映射与表名映射

id生成策略

不同的表应用不同的id生成策略

  • 日志:自增(1,2,3,4,..)
  • 购物订单:特殊规则(FQ23948AK3843)
  • 外卖单:关联地区日期等信息(10 04 20200314 34 91)
  • 关系表:可省略id

package com.xxx.domain;

@Data
@TableName("tbl_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(value = "pwd", select = false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
}

mybatis-plus:
  configuration:
    log-impl: org.apache.itbatis.logging.stdout.StdOutImpl
  global-config:
    banner: false
    db-config:
      id-type: assign_id # 实体类中表名不需要在使用@TableId注解
      table-prefix: tbl_ # 给实体类类名加前缀

多数据操作

逻辑删除

删除操作业务问题:业务数据从数据库中丢弃 逻辑删除:为数据设置是否可用状态字段,删除时设置状态字段为不可用状态,数据保留在数据库中

package com.xxx.domain;

@Data
@TableName("tbl_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(value = "pwd", select = false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
    // 逻辑删除字段,标记当前记录是否被删除
    @TableLogic(value = "0", delval = "1")
    private Integer delted;
}
mybatis-plus:
  configuration:
    log-impl: org.apache.itbatis.logging.stdout.StdOutImpl
  global-config:
    banner: false
    db-config:
      id-type: assign_id # 实体类中表名不需要在使用@TableId注解
      table-prefix: tbl_ # 给实体类类名加前缀
      logic-delete-field: deleted # 不需要写@TableLogic注解
      logic-not-delete-value: 0
      logic-deletec-value: 1

乐观锁

package com.xxx.domain;

@Data
@TableName("tbl_user")
public class User {
    @TableId(type = IdType.AUTO)
    private Long id;
    private String name;
    @TableField(value = "pwd", select = false)
    private String password;
    private Integer age;
    private String tel;
    @TableField(exist = false)
    private Integer online;
    // 逻辑删除字段,标记当前记录是否被删除
    @TableLogic(value = "0", delval = "1")
    private Integer delted;
    @Version
    private Integer version;

}
package com.xxx.config;

@Configuration
public class MpConfig {

    @Bean
    public MybatisPlusInterceptor mpInterceptor() {
        // 1.定义mp拦截器
        MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor();
        // 2.添加具体的拦截器
        mpInterceptor.addInnerInterceptor(new PaginationInnerInterceptor());
        // 3.添加乐观锁拦截器
        mpInterceptor.addInnerInterceptor(new OptimistickLockerInnerInterceptor());
        return mpInterceptor;
    }
}
@SpringBootTest
class MybatisplusApplicationTests {
    @Autowired
    private UserDao userDao;

    @Test
    void testUpdate() {
        //User user = new User();
        //user.setId(3L);
        //user.setName("Jock666");
        //user.setVersion(1);      // 需要提供version才能使用
        //userDao.updateById(user);

        // 1. 先通过要修改的数据Id将当前数据查询出来
        //User user = userDao.selectById(3L);
        // 2. 将要修改的属性逐一设置进去
        //user.setName("Jock888");
        //userDao.updateById(user);

        // 1. 先通过要修改的数据Id将当前数据查询出来
        User user = userDao.selectById(3L); //version=3

        User user2 = userDao.selectById(3L); // version=3

        user2.setName("Jock888");
        userDao.updateById(user2); //version=4

        user.setName("Jock888");
        userDao.updateById(user);  //version=3?条件还成立吗?


    }
}

代码生成器

  • 模板:MyBatisPlus提供
  • 数据库相关配置:读取数据库获取信息
  • 开发者自定义配置:手工配置
package com.xxx;

public class Generator {
    public static void main(String[] args) {
        AutoGenerator AutoGenerator = new AutoGenerator();

        DataSourceConfig dataSource = new DataSourceConfig();
        dataSource.setDriverName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/mybatisplus_db?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        autoGenerator.setDataSource(dataSource);
        autoGenerator.execute();
    }
}

alt text alt text alt text alt text alt text alt text

帮助

mdBook Documentation