Spring完整知识三(完结)

Spring集成MyBatis

  • 注意
    • Spring注解形式集成MyBatis时,若SQL语句比较复杂则仍采用映射文件形式书写SQL语句;反之则用注解形式书写SQL语句,具体可详见Spring注解形式

环境准备相同步骤

  • Step1: 导入相关坐标,完整pom.xml文件代码如下

    • 导入Spring基础坐标:spring-context
    • 导入Spring提供的监听器ContextLoaderListener的相关坐标:spring-web
    • 导入Spring集成Web环境相关坐标:servlet、jsp
    • 导入Spring注解相关坐标:Annotation
    • 导入数据库相关坐标:mysql、数据源坐标(druid、cp30)
    • 导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring
    <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 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>org.example</groupId>
      <artifactId>MyBatisDemoTwo</artifactId>
      <packaging>war</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>MyBatisDemoTwo Maven Webapp</name>
      <url>http://maven.apache.org</url>
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>3.8.1</version>
          <scope>test</scope>
        </dependency>
        <!--===================Spring基础坐标=======================-->
        <!--spring坐标-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>6.1.6</version>
        </dependency>
        <!--===================Spring自带监听器ContextLoaderListener所需坐标=======================-->
        <!--spring-web-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>5.2.25.RELEASE</version>
        </dependency>
    
        <!--===================Spring集成Web环境相关坐标=======================-->
        <!-- servlet-->
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.1</version>
          <scope>provided</scope>
        </dependency>
    
        <!--jsp-->
        <dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>javax.servlet.jsp-api</artifactId>
          <version>2.3.3</version>
          <scope>provided</scope>
        </dependency>
    
        <!--===================Spring注解相关坐标=======================-->
        <!--Annotation坐标-->
        <dependency>
          <groupId>javax.annotation</groupId>
          <artifactId>javax.annotation-api</artifactId>
          <version>1.3.2</version>
        </dependency>
    
        <!--=====================数据库相关坐标=========================-->
        <!--mysql坐标-->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.33</version>
        </dependency>
    
        <!--druid坐标-->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.18</version>
        </dependency>
    
        <!--c3p0坐标-->
        <dependency>
          <groupId>com.mchange</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.5.5</version>
        </dependency>
        
        <!--=====================MyBatis相关坐标=========================-->
        <!--MyBatis坐标-->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.16</version>
        </dependency>
        <!--mybatis-spring-->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>3.0.3</version>
        </dependency>
        <!--spring-jdbc-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>6.1.10</version>
        </dependency>
    
      </dependencies>
      <build>
        <finalName>MyBatisDemoTwo</finalName>
        <plugins>
          <!-- Tomcat插件 -->
          <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
          </plugin>
        </plugins>
      </build>
    </project>
    
  • Step2: 右键源代码配置文件目录(即资源文件resources)→NewFile,创建properties配置文件,博主文件名为jdbc.properties,该配置文件代码如下

    • 注意: properties配置文件中配置的各个属性前必须添加个id.(即id.属性,比如:属性url就设置为id.url,博主设置的为jdbc.url),以供Spring配置文件可以使用属性占位符${} 语法引用这些属性
    #driverClassName代表数据库驱动,后跟驱动全类名(在MySQL驱动jar包下的META-INF下的services文件夹下的java.sql.Driver文件内)
    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    # 数据库连接URL
    jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    # 数据库用户名
    jdbc.username=root
    # 数据库密码
    jdbc.password=123456
    # 初始化连接数量---即容器中初始的数据库连接数量
    jdbc.initialSize=5
    # 最大活跃连接数量---容器中初始为5个,但若5个用完了,此时可以在申请5个数据库连接数量
    #也就是说容器中最多存放10个数据库连接
    jdbc.maxActive=10
    # 获取连接时的最大等待时间,单位:毫秒。---与数据库进行连接时若超过3s仍未连接成功,则会报错
    jdbc.maxWait=3000
    #最小空闲连接数量---minIdle=5
    # 配置检测连接是否有效的SQL,可以是一个查询语句,如果不指定则默认为"SELECT 1"---validationQuery=SELECT 1
    # 是否开启自动提交事务---defaultAutoCommit=true
    
  • Step3: 创建数据库表tb_brand 并使IDEA与数据库建立连接 ,SQL代码如下

    DROP TABLE IF EXISTS tb_brand;
    
    -- 创建品牌表brand
    CREATE TABLE IF NOT EXISTS tb_brand
    (
    	-- id 主键
    	id int PRIMARY KEY auto_increment,
    	-- 品牌名称
    	brand_name VARCHAR(20),
    	-- 企业名称
    	company_name VARCHAR(20),
    	-- 排序字段
    	ordered INT,
    	-- 描述信息
    	description VARCHAR(100),
    	-- 状态:0:禁用  1:启用
    	status INT
    );
    
    -- 添加数据
    INSERT INTO tb_brand(brand_name, company_name, ordered, description, status) 
    VALUES ('三只松鼠', '三只松鼠股份有限公司', 5, '好吃不上火', 0),
    			 ('华为', '华为技术有限公司', 100, '华为致力于构建万物互联的世界', 1),
           ('小米', '小米科技有限公司', 50, 'Are you OK', 1);
    
    SELECT * FROM tb_brand;
    

Spring注解形式

项目完整框架图

在这里插入图片描述

环境准备

  • Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类Brand,代码所如下

    package at.guigu.pojo;
    
    import org.apache.ibatis.type.Alias;
    
    @Alias("brand")
    public class Brand {
        // id 主键
        private Integer id;
        // 品牌名称
        private String brandName;
        // 企业名称
        private String companyName;
        // 排序字段 用于将某个品牌显示在最前面让消费者看到
        private Integer ordered;
        // 描述信息
        private String description;
        // 状态:0:禁用  1:启用
        private Integer status;
    
        public Brand() {}
    
        public Brand(Integer id, String brandName, String companyName, Integer ordered, String description, Integer status) {
            this.id = id;
            this.brandName = brandName;
            this.companyName = companyName;
            this.ordered = ordered;
            this.description = description;
            this.status = status;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getBrandName() {
            return brandName;
        }
    
        public void setBrandName(String brandName) {
            this.brandName = brandName;
        }
    
        public String getCompanyName() {
            return companyName;
        }
    
        public void setCompanyName(String companyName) {
            this.companyName = companyName;
        }
    
        public Integer getOrdered() {
            return ordered;
        }
    
        public void setOrdered(Integer ordered) {
            this.ordered = ordered;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public Integer getStatus() {
            return status;
        }
    
        public void setStatus(Integer status) {
            this.status = status;
        }
    
        @Override
        public String toString() {
            return "Brand{" +
                    "id=" + id +
                    ", brandName='" + brandName + '\'' +
                    ", companyName='" + companyName + '\'' +
                    ", ordered=" + ordered +
                    ", description='" + description + '\'' +
                    ", status=" + status +
                    '}';
        }
    }
    
  • Step2: 创建一个与三层架构包同级的config包,并在该包下创建拆分配置文件对应的数据源拆分类DataSourceConfiguration,代码如下(以Druid为例)

    package at.guigu.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.PropertySource;
    
    import javax.sql.DataSource;
    
    
    // 分配置文件对应的类不用配置@Configuration以及@ComponentScan注解
    // 加载properties配置文件<context:property-placeholder location="classpath:jdbc.properties"/>
    @PropertySource("classpath:jdbc.properties")
    public class DataSourceConfiguration {
        @Value("${jdbc.driverClassName}")
        private String driverClassName;
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
    
        /**
         * Druid对应的bean
         * Spring会将当前方法的返回值以指定的id存储到Spring的IOC容器中
         * @return
         * @throws Exception
         */
        @Bean("dataSourceDruid")
        public DataSource getDruidDataSource() throws Exception{
            // 创建数据源对象
            DruidDataSource dataSource = new DruidDataSource();
            // 设置数据源基本连接数据
            dataSource.setDriverClassName(driverClassName);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    }
    
  • Step3: 在config包下创建拆分配置文件MyBatis对应的拆分类MyBatisConfiguration,代码如下

    package at.guigu.config;
    
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.mapper.MapperScannerConfigurer;
    import org.springframework.context.annotation.Bean;
    
    import javax.sql.DataSource;
    
    public class MyBatisConfiguration {
        
        @Bean
        public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            // 相当于设置别名<package name="at.guigu.pojo"/>
            sqlSessionFactoryBean.setTypeAliasesPackage("at.guigu.pojo");
            // 相当于配置数据库连接信息
            sqlSessionFactoryBean.setDataSource(dataSource);
            return sqlSessionFactoryBean;
        }
        // 映射扫描配置类,相当于引入dao包下所有接口对应的SQL映射文件<package name="at.guigu.dao"/>
        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer() {
            MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
            mapperScannerConfigurer.setBasePackage("at.guigu.dao");
            return mapperScannerConfigurer;
        }
    }
    
  • Step4: 在config包下创建Spring主配置文件对应的主类SpringConfiguration,并引入分配置文件对应的拆分类DataSourceConfiguration以及MyBatisConfiguration,代码如下

    package at.guigu.config;
    
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    
    // 该注解代表该类是Spring的核心配置类
    @Configuration
    // 配置注解的组件扫描<context:component-scan base-package="at.guigu"></context:component-scan>
    @ComponentScan("at.guigu")
    @MapperScan("at.guigu.dao")
    // 引入拆分配置文件<import resource="applicationContext-xxx.xml"/>
    @Import({DataSourceConfiguration.class, MyBatisConfiguration.class})
    public class SpringConfiguration {
    }
    
  • Step5: 创建三层架构包,且初始代码分别如下

    • 在持久层dao包下创建BrandDao接口

      package at.guigu.dao;
      
      import org.apache.ibatis.annotations.Mapper;
      
      @Mapper
      public interface BrandDao {
          
      }
      

      @Mapper注解作用: 用于标记单个Mapper接口,让MyBatis生成它的实现类并注入到Spring容器中。也就是说此时不需要创建持久层的实现类,有IOC容器自动创建,其唯一标识id为对应接口名首字母大写(即brandDao

      该注解也可以使用@MapperScan(at.guigu.dao)来代替,不过该注解需写在Spring的核心配置文件中,表示: 用于扫描指定包中的所有接口,将它们自动注册为Spring的Bean并作为Mapper

      以上两个注解均在示例中演示,做项目时可根据实际情况选择

    • 在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与BrandDao接口对应的目录下创建 SQL映射文件BrandDao.xml ,如图所示,SQL映射文件代码如下所示

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <!--namespace:名称空间-->
      <mapper namespace="at.guigu.dao.BrandDao">
          <!--结果映射-->
          <resultMap id="brandResultMap" type="brand">
              <!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可-->
              <result column="brand_name" property="brandName"/>
              <result column="company_name" property="companyName"/>
          </resultMap>
      </mapper>
      
      • 注意:在MyBatis配置类的形式中,sqlSessionFactoryBean.setTypeAliasesPackage()方法设置别名无效,目前还未知原因,所以结果映射中的type="brand"会标红报错,所以解决办法为:利用@Alias("别名")注解为类的全类名设置类型别名
    • 在业务层service包下创建BrandService类,初始代码如下

      package at.guigu.service;
      
      import at.guigu.dao.BrandDao;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      
      @Service("brandService")
      public class BrandService {
          @Autowired
          private BrandDao brandDao;
      
      }
      
    • 在表现层web包下创建BrandServlet类,初始代码如下

      package at.guigu.web;
      
      import javax.servlet.*;
      import javax.servlet.http.*;
      import java.io.IOException;
      
      public class BrandServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
          }
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doGet(request, response);
          }
      }
      
  • Step6: 在web项目核心目录(即WEB-INF)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener 监听器、web配置。完整代码如下

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <!--配置Spring配置类的全局初始化参数-->
      <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </context-param>
      <context-param>
        <!--定义参数的名称,必须是唯一的-->
        <param-name>contextConfigLocation</param-name>
        <!--定义参数的值-->
        <param-value>at.guigu.config.SpringConfiguration</param-value>
      </context-param>
    
      <!--监听器-->
      <!--配置Spring所提供的`ContextLoaderListener` 监听器-->
      <listener>
        <!--监听器类的全限定名-->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <!--声明一个Servlet-->
      <servlet>
        <!--声明的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--声明的Servlet的全限定名-->
        <servlet-class>at.guigu.web.BrandServlet</servlet-class>
      </servlet>
    
      <!--URL模式映射到特定的Servlet上(即BrandServlet-->
      <servlet-mapping>
        <!--指定的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--给指定的Servlet设置url,相当于@WebServlet("/brandServle")-->
        <url-pattern>/brandServlet</url-pattern>
      </servlet-mapping>
    </web-app>
    

查询所有数据

SpringMVC注解开发形式仅以查询所有数据示例,其它增删改操作可根据MyBatis完整知识点汇总示例操作

  • Step1: 在dao包下的BrandDao接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句

    注意:简单查询SQL语句采用注解方式,复杂SQL语句采用映射文件方式

    • BrandDao接口代码如下

      package at.guigu.dao;
      
      import at.guigu.pojo.Brand;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      import org.apache.ibatis.annotations.Select;
      
      import java.util.List;
      import java.util.Map;
      
      @Mapper
      public interface BrandDao {
          // 查询所有条数据
          @Select("select * from tb_brand")
          List<Brand> all();
          // 查询单条数据
          @Select("select * from tb_brand where id = #{id}")
          Brand selectById(@Param("id") int id);
          //静态单条件查询
          @Select("select * from tb_brand where id > #{id}")
          List<Brand> selectBySingleConOne(int id);
          // 动态单条件查询——对象参数接收
          List<Brand> selectBySingleConTwo(Brand brand);
          // 动态单条件查询——Map集合参数接收
          List<Brand> selectBySingleConTwoo(Map map);
          // 动态多条件查询——对象参数接收
          List<Brand> selectByMaxConOne(Brand brand);
          // 动态多条件查询——Map集合参数接收
          List<Brand> selectByMaxConTwo(Map map);
      }
      

      注意:在java文件中利用注解形式书写SQL语句时,不需要对特殊字符进行转义

    • SQL映射文件BrandDao.xml文件代码如下

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <!--namespace:名称空间-->
      <mapper namespace="at.guigu.dao.BrandDao">
          <!--结果映射-->
          <resultMap id="brandResultMap" type="brand">
              <!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可-->
              <result column="brand_name" property="brandName"/>
              <result column="company_name" property="companyName"/>
          </resultMap>
      
          <!--动态单条件查询——对象参数接收-->
          <select id="selectBySingleConTwo" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <choose> <!--类似于switch-->
                      <when test="status != null">
                          status = #{status}
                      </when>
                      <when test="companyName != null and companyName != ''">
                          company_name like #{companyName}
                      </when>
                      <when test="brandName != null and brandName != ''">
                          brand_name like #{brandName}
                      </when>
                  </choose>
              </where>
          </select>
          <!--动态单条件查询——Map集合参数接收-->
          <select id="selectBySingleConTwoo" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <choose> <!--类似于switch-->
                      <when test="status != null">
                          status = #{status}
                      </when>
                      <when test="companyName != null and companyName != ''">
                          company_name like #{companyName}
                      </when>
                      <when test="brandName != null and brandName != ''">
                          brand_name like #{brandName}
                      </when>
                  </choose>
              </where>
          </select>
      
          <!--动态多条件查询——对象参数接收-->
          <select id="selectByMaxConOne" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <if test="status != null">
                      and status = #{status}
                  </if>
                  <if test="companyName != null and companyName != ''">
                      and company_name like #{companyName}
                  </if>
                  <if test="brandName != null and brandName != ''">
                      and brand_name like #{brandName}
                  </if>
              </where>
          </select>
          <!--动态多条件查询——Map集合参数接收-->
          <select id="selectByMaxConTwo" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <if test="status != null">
                      and status = #{status}
                  </if>
                  <if test="companyName != null and companyName != ''">
                      and company_name like #{companyName}
                  </if>
                  <if test="brandName != null and brandName != ''">
                      and brand_name like #{brandName}
                  </if>
              </where>
          </select>
      
      </mapper>
      
  • Step2: 在业务层service包下的BrandService类来调用dao包下的BrandDao接口中的方法,代码如下

    package at.guigu.service;
    
    import at.guigu.dao.BrandDao;
    import at.guigu.pojo.Brand;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    import java.util.Map;
    
    @Service("brandService")
    public class BrandService {
        @Autowired
        private BrandDao brandDao;
        // 查询所有条数据
        public List<Brand> getAll() {
            return brandDao.all();
        }
        // 查询单条数据
        public Brand getById(int id) {
            return brandDao.selectById(id);
        }
        // 静态单条件查询
        public List<Brand> selectBySingleConOne(int id) {
            return brandDao.selectBySingleConOne(id);
        }
        // 动态单条件查询——对象参数接收
        public List<Brand> selectBySingleConTwo(Brand brand) {
            return brandDao.selectBySingleConTwo(brand);
        }
        // 动态单条件查询——Map集合参数接收
        public List<Brand> selectBySingleConTwoo(Map map) {
            return brandDao.selectBySingleConTwoo(map);
        }
        // 动态多条件查询——对象参数接收
        public List<Brand> selectByMaxConOne(Brand brand) {
            return brandDao.selectByMaxConOne(brand);
        }
        // 动态多条件查询——Map集合参数接收
        public List<Brand> selectByMaxConTwo(Map map){
            return brandDao.selectByMaxConTwo(map);
        }
    
    }
    
  • Step3: 在表现层web包下的BrandServlet类中来调用业务层service包中的BrandService类中的方法,代码如下:

    package at.guigu.web;
    
    import at.guigu.pojo.Brand;
    import at.guigu.service.BrandService;
    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    import java.util.List;
    
    public class BrandServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 1 获取最大域ServletContext对象
            ServletContext servletContext = request.getServletContext();
            //等同于ServletContext servletContext = request.getSession().getServletContext();
    
            // 2 获取应用上下文对象(即IOC容器)
            ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    
            //3 获取bean
            BrandService brandService = app.getBean(BrandService.class);
            //等同于BookServicel bookService = (BookService) app.getBean("bookService");
    
            //4 调用方法执行SQL语句
            List<Brand> brands = brandService.getAll();
            for (Brand brand : brands) {
                System.out.println(brand);
            }
        }
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    

    Tomcat运行截图如下

    在这里插入图片描述

  • 注意:由于此时已经配置了映射扫描的类,所以此时MyBatis会自动扫描at.guigu.dao包下的所有接口,并为它们生成相应的代理对象,而不需要使用@Mapper注解或@MapperScan注解,此处添加上是为了知道有两个注解可以用来自动生成持久层接口对应的bean

Spring配置文件形式

项目完整框架图

在这里插入图片描述

环境准备

  • Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类Brand,代码所如下

    package at.guigu.pojo;
    
    public class Brand {
        // id 主键
        private Integer id;
        // 品牌名称
        private String brandName;
        // 企业名称
        private String companyName;
        // 排序字段 用于将某个品牌显示在最前面让消费者看到
        private Integer ordered;
        // 描述信息
        private String description;
        // 状态:0:禁用  1:启用
        private Integer status;
    
        public Brand() {}
    
        public Brand(Integer id, String brandName, String companyName, Integer ordered, String description, Integer status) {
            this.id = id;
            this.brandName = brandName;
            this.companyName = companyName;
            this.ordered = ordered;
            this.description = description;
            this.status = status;
        }
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getBrandName() {
            return brandName;
        }
    
        public void setBrandName(String brandName) {
            this.brandName = brandName;
        }
    
        public String getCompanyName() {
            return companyName;
        }
    
        public void setCompanyName(String companyName) {
            this.companyName = companyName;
        }
    
        public Integer getOrdered() {
            return ordered;
        }
    
        public void setOrdered(Integer ordered) {
            this.ordered = ordered;
        }
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public Integer getStatus() {
            return status;
        }
    
        public void setStatus(Integer status) {
            this.status = status;
        }
    
        @Override
        public String toString() {
            return "Brand{" +
                    "id=" + id +
                    ", brandName='" + brandName + '\'' +
                    ", companyName='" + companyName + '\'' +
                    ", ordered=" + ordered +
                    ", description='" + description + '\'' +
                    ", status=" + status +
                    '}';
        }
    }
    
  • Step2: 右键源代码配置文件目录(即资源文件resources)→NewFile,创建MyBatis核心配置文件(名为mybatis-config.xml),代码如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <!--设置别名-->
        <typeAliases>
            <package name="at.guigu.pojo"/>
        </typeAliases>
    
    </configuration>
    
  • Step3: 右键源代码配置文件目录(即资源文件resources)→NewXML Configuration FileSpring Config,创建Spring核心配置文件(名为applicationContext.xml),默认代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    </beans>
    
    • Step3-1: 使用context命名空间加载 jdbc.properties 文件(前提:需引入context命名空间和约束路径)
      • context命名空间:xmlns:context="http://www.springframework.org/schema/context"
      • context约束路径:http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
    • Step3-2: 配置数据源对应的bean
    • Step3-3: 配置MyBatis的SqlSessionFactory
      • 配置数据源
      • 配置MyBatis核心配置文件
      • 配置别名
        • 由于通过Spring的核心配置文件配置别名无效,所以必须在MyBatis核心配置文件中设置别名,然后在Spring的核心配置文件中配置MyBatis核心配置文件
    • Step3-4: 引入dao包下所有接口对应的SQL映射文件
      • 此时Spring会进行持久层扫描,自动生成该层中对应接口的bean
    <?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"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--使用`context`命名空间加载 `properties` 文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <!--Druid对应的bean-->
        <bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource">
            <!--使用属性占位符`${}`语法引用properties文件中的属性-->
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        <!--配置MyBatis的SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--配置数据源-->
            <property name="dataSource" ref="dataSourceDruid"/>
            <!--加载MyBatis的核心配置文件-->
            <property name="configLocation" value="mybatis-config.xml"/>
            <!--配置别名-->
            <property name="typeAliasesPackage" value="at.guigu.pojo"/>
        </bean>
        <!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的bean-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="at.guigu.dao"/>
        </bean>
    
    </beans>
    
  • Step4: 创建三层架构包,且初始代码分别如下

    • 在持久层dao包下创建BrandDao接口

      package at.guigu.dao;
      
      public interface BrandDao {
      }
      
    • 在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与BrandDao接口对应的目录下创建 SQL映射文件BrandDao.xml ,如图所示,SQL映射文件代码如下所示

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <!--namespace:名称空间-->
      <mapper namespace="at.guigu.dao.BrandDao">
          <!--结果映射-->
          <resultMap id="brandResultMap" type="brand">
              <!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可-->
              <result column="brand_name" property="brandName"/>
              <result column="company_name" property="companyName"/>
          </resultMap>
      </mapper>
      
    • 在业务层service包下创建BrandService类,初始代码如下

      package at.guigu.service;
      
      import at.guigu.dao.BrandDao;
      
      public class BrandService {
          private BrandDao brandDao;
          
          public void setBrandDao(BrandDao brandDao) {
              this.brandDao = brandDao;
          }
          
      }
      

      注意:此处使用的是setter方法注入,所以必须在业务层中用setter方法将IOC容器中自动生成的持久层接口对应的bean注入到业务层对应的类中

    • 在表现层web包下创建BrandServlet类,初始代码如下

      package at.guigu.web;
      
      import javax.servlet.*;
      import javax.servlet.http.*;
      import java.io.IOException;
      
      public class BrandServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
          }
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doGet(request, response);
          }
      }
      
  • Step5: 在web项目核心目录(即WEB-INF)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener 监听器、web配置。完整代码如下

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <!--配置Spring配置类的全局初始化参数-->
      <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </context-param>
      <context-param>
        <!--定义参数的名称,必须是唯一的-->
        <param-name>contextConfigLocation</param-name>
        <!--定义参数的值-->
        <param-value>at.guigu.config.SpringConfiguration</param-value>
      </context-param>
    
      <!--监听器-->
      <!--配置Spring所提供的`ContextLoaderListener` 监听器-->
      <listener>
        <!--监听器类的全限定名-->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <!--声明一个Servlet-->
      <servlet>
        <!--声明的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--声明的Servlet的全限定名-->
        <servlet-class>at.guigu.web.BrandServlet</servlet-class>
      </servlet>
    
      <!--将URL模式映射到特定的Servlet上(即BrandServlet)-->
      <servlet-mapping>
        <!--指定的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--给指定的Servlet设置url,相当于@WebServlet("/brandServlet")-->
        <url-pattern>/brandServlet</url-pattern>
      </servlet-mapping>
    </web-app>
    

查询所有数据

SpringMVC注解开发形式仅以查询所有数据示例,其它增删改操作可根据MyBatis完整知识点汇总示例操作

  • Step1: 在dao包下的BrandDao接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句

    • BrandDao接口代码如下

      package at.guigu.dao;
      
      import at.guigu.pojo.Brand;
      
      import java.util.List;
      import java.util.Map;
      
      public interface BrandDao {
          // 查询所有条数据
          List<Brand> all();
          // 查询单条数据:通过id查询
          Brand selectById(int id);
          // 静态单条件查询
          List<Brand> selectBySingleConOne(int id);
          // 动态单条件查询——对象参数接收
          List<Brand> selectBySingleConTwo(Brand brand);
          // 动态单条件查询——Map集合参数接收
          List<Brand> selectBySingleConTwoo(Map map);
          // 动态多条件查询——对象参数接收
          List<Brand> selectByMaxConOne(Brand brand);
          // 动态多条件查询——Map集合参数接收
          List<Brand> selectByMaxConTwo(Map map);
      }
      
    • SQL映射文件BrandDao.xml文件代码如下

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <!--namespace:名称空间-->
      <mapper namespace="at.guigu.dao.BrandDao">
          <!--结果映射-->
          <resultMap id="brandResultMap" type="brand">
              <!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可-->
              <result column="brand_name" property="brandName"/>
              <result column="company_name" property="companyName"/>
          </resultMap>
      
          <!--查询所有条数据
          resultMap属性值为结果映射的id属性值
          -->
          <select id="all" resultMap="brandResultMap">
              select * from tb_brand;
          </select>
          <!--查询单条数据:通过id查询
          parameterType可设置传入的参数类型 ,它可以省略不写
          -->
          <select id="selectById" parameterType="int" resultMap="brandResultMap">
              select * from tb_brand where id = #{id};
          </select>
          <!--静态单条件查询-->
          <select id="selectBySingleConOne" parameterType="int" resultMap="brandResultMap">
              # 此处根据情况选择使用转义字符还是CDATA区
              select * from tb_brand where id <![CDATA[
              >
              ]]>
              #{id};
          </select>
      
          <!--动态单条件查询——对象参数接收-->
          <select id="selectBySingleConTwo" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <choose> <!--类似于switch-->
                      <when test="status != null">
                          status = #{status}
                      </when>
                      <when test="companyName != null and companyName != ''">
                          company_name like #{companyName}
                      </when>
                      <when test="brandName != null and brandName != ''">
                          brand_name like #{brandName}
                      </when>
                  </choose>
              </where>
          </select>
          <!--动态单条件查询——Map集合参数接收-->
          <select id="selectBySingleConTwoo" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <choose> <!--类似于switch-->
                      <when test="status != null">
                          status = #{status}
                      </when>
                      <when test="companyName != null and companyName != ''">
                          company_name like #{companyName}
                      </when>
                      <when test="brandName != null and brandName != ''">
                          brand_name like #{brandName}
                      </when>
                  </choose>
              </where>
          </select>
      
          <!--动态多条件查询——对象参数接收-->
          <select id="selectByMaxConOne" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <if test="status != null">
                      and status = #{status}
                  </if>
                  <if test="companyName != null and companyName != ''">
                      and company_name like #{companyName}
                  </if>
                  <if test="brandName != null and brandName != ''">
                      and brand_name like #{brandName}
                  </if>
              </where>
          </select>
          <!--动态多条件查询——Map集合参数接收-->
          <select id="selectByMaxConTwo" resultMap="brandResultMap">
              select * from tb_brand
              <where>
                  <if test="status != null">
                      and status = #{status}
                  </if>
                  <if test="companyName != null and companyName != ''">
                      and company_name like #{companyName}
                  </if>
                  <if test="brandName != null and brandName != ''">
                      and brand_name like #{brandName}
                  </if>
              </where>
          </select>
      </mapper>
      
  • Step2: 在业务层service包下的BrandService类来调用dao包下的BrandDao接口中的方法,代码如下

    package at.guigu.service;
    
    import at.guigu.dao.BrandDao;
    import at.guigu.pojo.Brand;
    
    import java.util.List;
    import java.util.Map;
    
    public class BrandService {
        private BrandDao brandDao;
        public void setBrandDao(BrandDao brandDao) {
            this.brandDao = brandDao;
        }
        // 查询所有条数据
        public List<Brand> getAll() {
            return brandDao.all();
        }
        // 查询单条数据
        public Brand getById(int id) {
            return brandDao.selectById(id);
        }
        // 静态单条件查询
        public List<Brand> selectBySingleConOne(int id) {
            return brandDao.selectBySingleConOne(id);
        }
        // 动态单条件查询——对象参数接收
        public List<Brand> selectBySingleConTwo(Brand brand) {
            return brandDao.selectBySingleConTwo(brand);
        }
        // 动态单条件查询——Map集合参数接收
        public List<Brand> selectBySingleConTwoo(Map map) {
            return brandDao.selectBySingleConTwoo(map);
        }
        // 动态多条件查询——对象参数接收
        public List<Brand> selectByMaxConOne(Brand brand) {
            return brandDao.selectByMaxConOne(brand);
        }
        // 动态多条件查询——Map集合参数接收
        public List<Brand> selectByMaxConTwo(Map map){
            return brandDao.selectByMaxConTwo(map);
        }
    }
    
  • Step3: 在Spring的核心配置文件中配置业务层对应的bean,并将持久层对应的bean依赖注入到该业务层中,代码如下

    <?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"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!--使用`context`命名空间加载 `properties` 文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <!--Druid对应的bean-->
        <bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource">
            <!--使用属性占位符`${}`语法引用properties文件中的属性-->
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
        <!--配置MyBatis的SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--配置数据源-->
            <property name="dataSource" ref="dataSourceDruid"/>
            <!--加载MyBatis的核心配置文件-->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <!--配置别名-->
            <property name="typeAliasesPackage" value="at.guigu.pojo"/>
        </bean>
    
        <!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的benan-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="at.guigu.dao"/>
        </bean>
    
        <bean id="brandService" class="at.guigu.service.BrandService">
            <!--在对应的业务层中必须有setter方法,否则name属性值会报错-->
            <property name="brandDao" ref="brandDao"/>
        </bean>
    
    </beans>
    
  • Step4: 在表现层web包下的BrandServlet类中来调用业务层service包中的BrandService类中的方法,代码如下:

    package at.guigu.web;
    
    import at.guigu.pojo.Brand;
    import at.guigu.service.BrandService;
    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    import java.util.List;
    
    public class BrandServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 1 获取最大域ServletContext对象
            ServletContext servletContext = request.getServletContext();
            //等同于ServletContext servletContext = request.getSession().getServletContext();
    
            // 2 获取应用上下文对象(即IOC容器)
            ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    
            //3 获取bean
            BrandService brandService = app.getBean(BrandService.class);
            //等同于BookServicel bookService = (BookService) app.getBean("bookService");
    
            //4 调用方法执行SQL语句
            List<Brand> brands = brandService.getAll();
            for (Brand brand : brands) {
                System.out.println(brand);
            }
        }
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    

    Tomcat运行截图如下

    在这里插入图片描述

AOP

  • AOP(Aspect-Oriented Programming,面向切面编程),是一种编程范式,它在Spring中可用于将那些与业务无关,但会对多个对象产生影响的公共行为和逻辑,抽取成通知复用,来降低耦合,提高系统的可维护性

    • 作用:在程序运行期间,可以在不修改源码的情况下对业务方法进行功能增强
    • 优势:减少重复代码,降低耦合,提高开发效率,并且便于维护
  • 底层原理

    • AOP底层是通过Spring提供的动态代理技术实现的。在运行期间,Spring框架会动态的监控切入点方法的执行,一旦监控到切入点方法被运行,它就会通过动态代理技术动态的生成切入点方法所在的目标对象的代理对象。然后它会根据通知类别,在代理对象的对应位置处将对应通知织入,从而完成完整的代码逻辑运行
  • AOP动态代理技术

    • JDK代理:它是基于接口的动态代理技术

      • 也就是说业务层必须有接口以及接口对应的实现类
      • 此时代理对象会实现目标接口
    • cglib代理:它是基于父类的动态代理技术

      • 也就是说业务层无接口,只有业务层类
      • 此时代理对象为业务层目标类的子类
    • 注意:Spring框架在底层会根据目标类是否实现了接口来决定采用哪种动态代理方式

    在这里插入图片描述

  • AOP涉及几个概念

    • 目标对象(Target): 原始功能去掉共性功能后所对应的类所产生的对象,这种对象是无法直接完成最终工作的

    • 代理(Proxy): 目标对象无法直接完成工作,需要对其进行功能回填,这就需要原始对象的代理对象实现

      • SpringAOP的 核心本质是采用代理模式实现的
    • 连接点(JoinPoint): 是程序执行过程中的任意位置,粒度为执行方法、抛出异常、设置变量等

      • 在SpringA0P中,指的是方法。(即在Spring中可以被增强的方法叫做连接点)
    • 切入点(Pointcut): 匹配连接点的式子(也就是指我们要对哪些连接点进行拦截的定义)

      • 在SpringAoP中,一个切入点可以只描述一个具体方法,也可以匹配多个方法,比如:
        1. 一个具体方法:com.itheima.dao包下的BookDao接口中的无形参无返回值的save方法
        2. 匹配多个方法:所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
    • 通知/增强(Advice): 指拦截到连接点之后要做的事情就是通知

      • 在切入点处执行的操作,也就是共性功能
      • 在SpringAoP中,功能最终以方法的形式呈现
    • 通知类: 定义通知的类

    • 切面(Aspect): 描述通知与切入点的对应关系,可以理解成通知与切入点的结合

    • 织入(Weaving): 指把通知/增强应用到目标对象来创建新的代理对象的过程。

      • Spring采用动态代理织入;AspectJ采用编译器织入和类装载期织入

在这里插入图片描述

快速入门

要求:在接口执行前输出当前系统时间

注意:本示例为简单基础示例,所以不在创建持久层dao包

环境准备

  • Step1: 导入坐标

    • 导入Spring基础坐标:spring-context

    • 导入Spring提供的监听器ContextLoaderListener的相关坐标:spring-web

    • 导入Spring集成Web环境相关坐标:servlet、jsp

    • 导入Spring注解相关坐标:Annotation

    • 导入与AOP相关的坐标:aop、aspectj

      • AOP坐标会在导入spring-context坐标后系统自动导入,如图所示

        在这里插入图片描述

    • 导入数据库相关坐标:mysql、数据源坐标(druid、cp30)

    • 导入Spring集成JUnit相关坐标:junit、spring-test

    • 导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring

    <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 http://maven.apache.org/maven-v4_0_0.xsd">
      <modelVersion>4.0.0</modelVersion>
      <groupId>org.example</groupId>
      <artifactId>SpringAOPDemo</artifactId>
      <packaging>war</packaging>
      <version>1.0-SNAPSHOT</version>
      <name>SpringAOPDemo Maven Webapp</name>
      <url>http://maven.apache.org</url>
      <dependencies>
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>3.8.1</version>
          <scope>test</scope>
        </dependency>
        <!--===================Spring基础坐标=======================-->
        <!--spring坐标-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>6.1.6</version>
        </dependency>
        <!--===================Spring自带监听器ContextLoaderListener所需坐标=======================-->
        <!--spring-web-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>5.2.25.RELEASE</version>
        </dependency>
    
        <!--===================Spring集成Web环境相关坐标=======================-->
        <!-- servlet-->
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>javax.servlet-api</artifactId>
          <version>4.0.1</version>
          <scope>provided</scope>
        </dependency>
    
        <!--jsp-->
        <dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>javax.servlet.jsp-api</artifactId>
          <version>2.3.3</version>
          <scope>provided</scope>
        </dependency>
    
        <!--===================Spring注解相关坐标=======================-->
        <!--Annotation坐标-->
        <dependency>
          <groupId>javax.annotation</groupId>
          <artifactId>javax.annotation-api</artifactId>
          <version>1.3.2</version>
        </dependency>
    
        <!--=====================AOP相关坐标=========================-->
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.9.22.1</version>
        </dependency>
    
        <!--=====================数据库相关坐标=========================-->
        <!--mysql坐标-->
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.33</version>
        </dependency>
    
        <!--druid坐标-->
        <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.2.18</version>
        </dependency>
    
        <!--c3p0坐标-->
        <dependency>
          <groupId>com.mchange</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.5.5</version>
        </dependency>
    
        <!--===================Spring集成junit相关坐标=======================-->
        <!--junit坐标-->
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.13.2</version>
          <scope>test</scope>
        </dependency>
        <!--spring-test坐标-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>6.1.6</version>
          <scope>test</scope>
        </dependency>
    
        <!--=====================MyBatis相关坐标=========================-->
        <!--MyBatis坐标-->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>3.5.16</version>
        </dependency>
        <!--mybatis-spring-->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>3.0.3</version>
        </dependency>
        <!--spring-jdbc-->
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>6.1.10</version>
        </dependency>
    
      </dependencies>
      <build>
        <finalName>SpringAOPDemo</finalName>
        <plugins>
          <!-- Tomcat插件 -->
          <plugin>
            <groupId>org.apache.tomcat.maven</groupId>
            <artifactId>tomcat7-maven-plugin</artifactId>
            <version>2.2</version>
          </plugin>
        </plugins>
      </build>
    </project>
    

基于XML的AOP开发

  • 步骤

    • 导入相关坐标(略)

    • 创建目标接口和目标类

    • 创建通知类(即切面类,内含通知/增强方法)

    • 将目标类与通知类的对象创建权交给Spring

      • 在Spring核心配置文件中配入织入关系
    • 代码测试

  • Step1: 创建三层架构包中的业务层service包及相关接口和实现类,代码如下

    • 业务层接口及其实现类

      package at.guigu.service;
      public interface BrandService {
          void save();
          void update();
      }
      
      package at.guigu.service.impl;
      import at.guigu.service.BrandService;
      
      public class BrandServiceImpl implements BrandService {
          @Override
          public void save() {
              System.out.println("bookDao save...");
          }
          @Override
          public void update() {
              System.out.println("bookDao update...");
          }
      }
      
  • Step2: 创建一个与三层架构包同级的aop包,并在该包下创建MyAdvice通知(即切面)类,代码如下

    package at.guigu.aop;
    
    public class MyAdvice {
        
        public void method1() {
            System.out.println(System.currentTimeMillis());
        }
    }
    
  • Step3: 右键源代码配置文件目录(即资源文件resources)→NewXML Configuration FileSpring Config,文件名为applicationContext.xml,默认代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    </beans>
    
    
    • Step3-1: 在Spring的核心配置文件中引入AOP的命名空间:xmlns:aop="http://www.springframework.org/schema/aop"
    • Step3-2: 在Spring的核心配置文件中引入AOP的约束路径:http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd
    • Step3-3: 配置业务层bean(该示例并未创建持久层,所以不用配置持久层bean)
    • Step3-4: 配置切面类(即通知类)对应的bean
    • Step3-5: 配置织入关系:将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上
      • 配置普通增强使用<aop:aspect>标签
      • 配置事务的增强使用<aop:advisor>标签,可详见事务控制部分内容
<?xml version="1.0" encoding="UTF-8"?>

<!--配置BrandService实现类bean  即目标对象-->
<bean id="brandService" class="at.guigu.service.impl.BrandServiceImpl"/>

<!--配置通知类(即切面类)对应的bean,即切面对象-->
<bean id="myAdvice" class="at.guigu.aop.MyAdvice"/>

<!--配置织入关系:即将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上-->
<aop:config>
    <!--将指定的bean配置为切面类(即通知类),ref属性值为要指定的切面类(即通知类)对应bean的id值-->
    <aop:aspect ref="myAdvice">
        <!--配置指定切面:切点+通知-->
        <aop:before method="method1" pointcut="execution(void at.guigu.service.BrandService.update())"></aop:before>
    </aop:aspect>
</aop:config>
```
  • Step4: 创建表现层web包,并在该包下创建BrandServlet类,代码如下

    package at.guigu.web;
    
    import at.guigu.service.BrandService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class BrandServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 1 获取最大域ServletContext对象
            ServletContext servletContext = request.getServletContext();
            //等同于ServletContext servletContext = request.getSession().getServletContext();
    
            // 2 获取应用上下文对象(即IOC容器)
            ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    
            //3 获取bean
            BrandService brandService = app.getBean(BrandService.class);
    
            //4 执行要增强的原始方法(即目标方法)
            brandService.update();
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    

    注意:由于此时获取的brean是代理对象,所以无法通过BrandServiceImpl branServiceImpl = app.getBean(BrandServiceImpl.class);BrandServiceImpl brandServiceImpl = (BrandServiceImpl) app.getBean("brandServiceImpl");来获取对应的bean。此时参数只能是接口的类对象。

  • Step5: 在web项目核心目录(即WEB-INF)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener 监听器、web配置。完整代码如下

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <!--全局初始化参数-->
      <context-param>
        <!--定义参数的名称,必须是唯一的-->
        <param-name>contextConfigLocation</param-name>
        <!--定义参数的值-->
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>
    
      <!--监听器-->
      <!--配置Spring所提供的`ContextLoaderListener` 监听器-->
      <listener>
        <!--监听器类的全限定名-->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
    
      <!--声明一个Servlet-->
      <servlet>
        <!--声明的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--声明的Servlet的全限定名-->
        <servlet-class>at.guigu.web.BrandServlet</servlet-class>
      </servlet>
    
      <!--将URL模式映射到特定的Servlet上(即BrandServlet)-->
      <servlet-mapping>
        <!--指定的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--给指定的Servlet设置url,相当于@WebServlet("/brandServlet")-->
        <url-pattern>/brandServlet</url-pattern>
      </servlet-mapping>
    </web-app>
    

    运行后截图如下

    在这里插入图片描述

基于注解的AOP开发

  • 步骤

    • 导入相关坐标(略)

    • 创建目标接口和目标类

    • 创建通知类(即切面类,内含通知/增强方法)

    • 将目标类与通知类的对象创建权交给Spring

      • 在通知类(即切面类中用注解配置织入关系)
    • 在Spring的核心配置类中开启组件扫描和AOP自动代理

    • 代码测试

  • Step1: 创建三层架构包中的业务层service包及相关接口和实现类,代码如下

    • 业务层接口及其实现类

      package at.guigu.service;
      
      public interface BrandService {
          void save();
          void update();
      }
      
      package at.guigu.service.impl;
      
      import at.guigu.service.BrandService;
      import org.springframework.stereotype.Service;
      
      @Service("bransServiceImpl")
      public class BrandServiceImpl implements BrandService {
          @Override
          public void save() {
              System.out.println(System.currentTimeMillis());
              System.out.println("bookDao save...");
          }
          @Override
          public void update() {
              System.out.println("bookDao update...");
          }
      }
      
  • Step2: 创建一个与三层架构包同级的aop包,并在该包下创建MyAdvice通知(即切面类),代码如下

    • Step2-1: 在该类中创建一个通知(即方法),并写入作为公共功能的内容

    • Step2-2: 在该类中用@Pointcut定义切入点

      • 切入点的定义依托一个不具有实际意义的方法(即无参数、无返回值,方法体内无实际逻辑)
    • Step2-3: 绑定切入点与通知的关系,并指定通知的执行位置。(即定义切面)

    • Step2-4: 为通知类添加@Component以及@Aspect注解

      • @Component:将切面类作为Spring容器中的一个Bean来管理
      • @Aspect:标记该类为切面类
    package at.guigu.aop;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAdvice {
    
        // 利用通知注解配置切面:切点+通知
        // 前置利用通知注解
        @Before("execution(void at.guigu.service.BrandService.update())")
        public void method1() {
            System.out.println(System.currentTimeMillis());
        }
    }
    
  • Step3: 创建一个与三层架构包同级的config包,并在该包下创建Spring的核心配置类SpringConfiguration,代码如下

    @EnableAspectJAutoProxy:启用Spring基于AspectJ 注解驱动的AOP功能

    package at.guigu.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @ComponentScan("at.guigu")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {
    }
    
  • Step4: 创建表现层web包,并在该包下创建BrandServlet类,代码如下

    package at.guigu.web;
    
    import at.guigu.service.BrandService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    
    public class BrandServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 1 获取最大域ServletContext对象
            ServletContext servletContext = request.getServletContext();
            //等同于ServletContext servletContext = request.getSession().getServletContext();
    
            // 2 获取应用上下文对象(即IOC容器)
            ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    
            //3 获取bean
            BrandService brandService = app.getBean(BrandService.class);
    
            //4 执行要增强的原始方法(即目标方法)
            brandService.update();
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    

    注意:由于此时获取的brean是代理对象,所以无法通过BrandServiceImpl branServiceImpl = app.getBean(BrandServiceImpl.class);BrandServiceImpl brandServiceImpl = (BrandServiceImpl) app.getBean("brandServiceImpl");来获取对应的bean。此时参数只能是接口的类对象。

  • Step5: 在web项目核心目录(即WEB-INF)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener 监听器、web配置。完整代码如下

    <!--配置Spring配置类的全局初始化参数-->
      <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </context-param>
      <context-param>
        <!--定义参数的名称,必须是唯一的-->
        <param-name>contextConfigLocation</param-name>
        <!--定义参数的值-->
        <param-value>at.guigu.config.SpringConfiguration</param-value>
      </context-param>
    
      <!--监听器-->
      <!--配置Spring所提供的`ContextLoaderListener` 监听器-->
      <listener>
        <!--监听器类的全限定名-->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <!--声明一个Servlet-->
      <servlet>
        <!--声明的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--声明的Servlet的全限定名-->
        <servlet-class>at.guigu.web.BrandServlet</servlet-class>
      </servlet>
    
      <!--将URL模式映射到特定的Servlet上(即BrandServlet)-->
      <servlet-mapping>
        <!--指定的Servlet的类名-->
        <servlet-name>BrandServlet</servlet-name>
        <!--给指定的Servlet设置url,相当于@WebServlet("/brandServlet")-->
        <url-pattern>/brandServlet</url-pattern>
      </servlet-mapping>
    

    运行后截图如下

    在这里插入图片描述

两种开发方式用到的标签及注解

基于XML的AOP开发

标签解释
<aop:config>Spring启动AOP配置
<aop:config>的内嵌标签解释
<aop:aspect ref>将指定的bean配置为切面类(即通知类),ref属性值为要指定的切面类(即通知类)对应bean的id值
<aop:aspect ref>的内嵌标签解释
<aop:pointcut id expression/>配置切入点,主要用于切入点表达式的抽取。id为切入点的唯一标识符;expression为指定切入点的表达式
<aop:通知类型 method pointcut-ref></aop:before>配置指定切面。aop的:后为通知类型;method属性值为对应切面类(即通知类)中的通知方法名;pointcut-ref属性值为切入点的唯一标识
<aop:通知类型 method pointcut></aop:通知类型>配置指定切面。aop的:后为通知类型;method属性值为对应切面类(即通知类)中的通知方法名;pointcut属性值为切入点表达式
  • 注意
    • 在不进行切入点表达式抽取时不需要使用<aop:pointcut id expression/>以及<aop:通知类型 method pointcut-ref></aop:before>这两个标签,反之则用这两个标签代替<aop:通知类型 method pointcut></aop:通知类型>标签
    • 切入点表达式抽取可详见切入点表达式部分内容

基于注解的AOP开发

与AOP相关的注解解释
@Component将对应类作为Spring容器中的一个Bean来管理
@Aspect标记切面类
@EnableAspectJAutoProxy启用Spring基于AspectJ 注解驱动的AOP功能
@通知类型("切点表达式")详见AOP通知类型部分内容

工作流程

  • AOP整个流程都是在动态代理模式下进行的

  • 步骤如下

    • Spring IOC容器启动

    • 读取所有切面 配置中 的切入点

      • 即不读取那些未与通知建立关系的切入点,只读取已经建立关系的切入点

      在这里插入图片描述

    • 初始化bean,判定bean对应类中的方法是否匹配到任意切入点

      • 若匹配失败则创建对象
      • 若匹配成功,则会创建原始对象(即目标对象)的代理对象
    • 获取bean执行方法

      • 获取bean,调用方法并执行,完整操作
      • 由于此时获取的bean是代理对象,所以它会根据代理对象的运行模式运行原始方法(即目标方法)与增强的内容,从而完成操作

切入点表达式

  • 切入点表法式在基于XML的AOP开发与基于注解的AOP开发的表达式分别如下

    • pointcut="execution(void at.guigu.service.BrandService.update())"
    • @Pointcut("execution(void at.guigu.service.BrandService.update())")
    • 其中execution(void at.guigu.service.BrandService.update())这一部分即为切入点表达式
  • 切入点表达式标准格式顺序:动作关键字(访问修饰符 返回值 包名.类/接口名.方法名(参数)异常名),其中:

    • 动作关键字:描述切入点的行为动作,比如:execution表示执行到指定切入点
    • 访问修饰符:public、private等,可省略
    • 异常名:方法定义中抛出指定异常,可省略
  • 在实际应用中,若严格按照切点表达式的格式顺序书写则比较麻烦,所以可使用通配符进行快速描述

    通配符解释
    *可用于匹配单个路径段或方法的名称、参数类型等。它可以独立出现,也可以作为前缀或后缀的匹配符出现
    ..可用于匹配包路径中的任意层级或方法中任意数量的参数。它可以独立出现,常用于简化包名与参数的书写。当其作为方法的形式参数出现时,代表该方法的参数可以为0,也可以是任意数量,比如:findId(..)
    +可用于匹配指定类及其所有子类或实现类
    • execution(public * at.guigu.*.BrandService.find*(*)):匹配at.guigu包下的任意包中的BrandService类或接口中所有 以find开头的带有一个参数的方法
      • 返回值为任意返回值,且方法必须有一个参数
    • execution(public Brand at..BrandService.findById(..)):匹配at包下及其任意子包中的BrandService类或接口中所有名称为findById且返回值为Brand的方法
      • 注意:方法参数可有可无,参数数量也可不固定
    • execution(* *..*Service+.*(..)):匹配任意返回值、任意包及其子包下的以Service结尾的类或接口的任意子类或实现类的包含任意参数的任意方法
      • 该表达式代表匹配业务层的所有方法
    • execution(* at.guigu.*.*Service.save(..)):匹配业务层下的所有save方法
  • 切入点表达式有两种描述方式

    • 执行at.guigu.service包下的BrandService接口中的无参数update方法:execution(void at.guigu.service.BrandService.update()),可写为(但不等同于)
      • execution(void at.*.*.*.update()):匹配返回值为void,at包下的任意子包下的任意包下的任意类/接口的update方法
      • execution(void *..update()):匹配返回值为void,任意包及其子包下的的任意update方法
      • execution(* *..u*(..)):匹配任意返回值,任意包及其子包下的以u开头的包含任意数量参数的任意方法
      • execution(* *..*e(..)):匹配任意返回值,任意包及其子包下的任意以e结尾的包含任意数量参数的任意方法
    • 执行at.guigu.service.impl包下的BrandServiceImpl类中的无参数update方法:execution(void at.guigu.service.impl.BrandServiceImpl.update()),可写为(但不等同于)
      • execution(void at.*.*.*.*.update()):匹配返回值为void,at包下的任意子包下的任意子包下的任意包下的任意类/接口的update方法
      • execution(void at..update()):匹配返回值为void,at包及其子包下的任意update方法
      • execution(* *..u*(..)):匹配任意返回值,任意包及其子包下的以u开头的包含任意数量参数的任意方法
      • execution(* *..*e(..)):匹配任意返回值,任意包及其子包下的任意以e结尾的包含任意数量参数的任意方法
  • 书写技巧(必须按标准规范开发,否则书写技巧失效)

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

切入点表达式的抽取

  • 当多个通知(即增强)的切入点表达式相同时,可以将切点表达式抽取,以此来降低耦合度

  • 基于XML的AOP开发的抽取方式

    • 在通知(即增强)中使用pointcut-ref属性来代替pointcut属性来引用抽取后的切入点表达式。代码如下

      <!--配置织入关系:即将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上-->
      <aop:config>
          <!--将指定的bean配置为切面类(即通知类),ref属性值为要指定的切面类(即通知类)对应bean的id值-->
          <aop:aspect ref="myAdvice">
              <!--配置切入点-->
              <aop:pointcut id="myPointcut" expression="execution(void at.guigu.service.BrandService.update())"/>
              <!--配置指定切面:切点+通知-->
              <aop:before method="method1" pointcut-ref="myPointcut"></aop:before>
              <!--配置指定切面:切点+通知
                  <aop:before method="method1" pointcut="execution(void at.guigu.service.BrandService.update())"></aop:before>
                  -->
          </aop:aspect>
      </aop:config>
      
  • 基于注解开发的抽取方式

    • 在通知类(即切面类)中定义一个方法,在该方法上使用@Pointcut注解定义切点表达式,然后在增强注解中进行引用即可。MyAdvive切面类(即通知类)代码更改如下

      package at.guigu.aop;
      
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class MyAdvice {
      
          // 定义切入点:即定义公共功能在哪执行
          //此处代表在update()方法处执行
          @Pointcut("execution(void at.guigu.service.BrandService.update())")
          private void pt(){}
      
          // 通知方法(即增强方法)
          // 抛出异常后通知
          @Before("pt()")
          public void method1() {
              System.out.println(System.currentTimeMillis());
          }
      }
      

AOP通知类型

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

    • 基于XML的AOP开发

      通知类型解释
      前置通知<aop:befrore>在原始方法(即目标方法)执行前执行
      后置通知<aop:after>在原始方法(即目标方法)执行后执行
      环绕通知<aop:around>可以决定是否执行原始方法(即目标方法)或修改返回值
      返回后通知<aop:after-returning>在原始方法(即目标方法)正常执行完毕后 执行
      抛出异常后通知<aop:throwing>只有在原始方法(即目标方法)运行抛出异常后才会执行
    • 基于注解的AOP开发

      通知类型解释
      前置通知@Before(value)在原始方法(即目标方法)执行前执行
      后置通知@After(value)在原始方法(即目标方法)执行后执行
      环绕通知@Around(value)可以决定是否执行原始方法(即目标方法)或修改返回值
      返回后通知@AfterReturning(value)在原始方法(即目标方法)正常执行完毕后 执行
      抛出异常后通知@AfterThrowing(value)只有在原始方法(即目标方法)运行抛出异常后才会执行

      以上五种通知类型的value值均为切入点方法名

  • 后置通知与返回后通知的区别

    • 后置通知在原始方法(即目标方法)执行完成之后执行,无论目标方法是正常返回结果还是抛出异常,它都会执行。类似于Java中的 finally 块,后置通知通常用于执行一些资源清理工作或记录日志,无论目标方法是否成功运行。
    • 返回后通知在原始方法(即目标方法)正常返回结果之后执行。如果原始方法(即目标方法)抛出异常,返回后通知不会执行

环绕通知

  • 环绕通知@Around是一种功能最强大的通知类型,它允许你在原始方法(即目标方法)执行之前和之后执行自定义逻辑,甚至可以决定是否执行原始方法(即目标方法)或修改返回值。

    • 可通过调用ProceedingJoinPoint中的proceed()方法来决定是否执行原始方法(即目标方法)

    • 可在原始方法(即目标方法)执行前后添加自定义逻辑

    • 可以捕获原始方法(即目标方法)的返回值并在返回之前进行修改

    • 可以捕获原始方法(即目标方法)抛出的异常并处理

  • 注意事项

    • 环绕通知必须依赖形参ProceedingjoinPoint才能实现对原始方法(即目标方法)的调用,进而实现原始方法(即目标方法)调用前后同时添加通知

    • 通知中如果未使用ProceedingJoinPoint对原始方法进行调用将跳过原始方法(即目标方法)的执行

    • 对原始方法(即目标方法)的调用可以不接收返回值,通知方法设置成void即可 ,如果接收返回值,必须设定为Object类型

    • 当通知方法设置为void时,对原始方法(即目标方法)就无法获取返回值

    • 原始方法(即目标方法)的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object

    • 由于无法预知原始方法(即目标方法)运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象

  • 可能用到的接口及其对应方法

    ProceedingjoinPoint接口中的方法解释
    Object proceed() throws Throwable决定是否执行原始方法(即目标方法)
    Object proceed(Object[] var1) throws Throwable决定是否执行原始方法(即目标方法),并给原始方法传入参数。(示例可见AOP通知获取数据部分)
    Signature getSignature()获取包含原始方法(即目标方法)签名信息的对象,包括方法名、声明类型、参数类型等。
    Signature接口中用到的方法解释
    String getName()获取原始方法(即目标方法)名称
    String toString()获取原始方法(即目标方法)的完整签名(即切入点表达式标准格式的内容)
    Class getDeclaringType()获取原始方法(即目标方法)所在类的Class对象
    String getDeclaringTypeName()获取始方法(即目标方法)所在类的全限定名

示例(基于注解形式)

本实例会以快速入门以基础进行演示

该示例以基于注解的AOP开发为例,可自行进行基于XML的AOP开发示例

  • 前置通知@Before,aop包下的MyAdvice类代码如下

    package at.guigu.aop;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAdvice {
    
        // 定义切入点:即定义公共功能在哪执行
        //此处代表在update()方法处执行
        @Pointcut("execution(void at.guigu.service.BrandService.update())")
        private void pt(){}
    
        // 通知类
        // 前置通知:在update方法执行前执行
        @Before("pt()")
        public void method1() {
            System.out.println(System.currentTimeMillis());
        }
    }
    

    在这里插入图片描述

  • 后置通知@After,aop包下的MyAdvice类代码如下

    package at.guigu.aop;
    
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAdvice {
    
        // 定义切入点:即定义公共功能在哪执行
        //此处代表在update()方法处执行
        @Pointcut("execution(void at.guigu.service.BrandService.update())")
        private void pt(){}
    
        // 通知类
        // 前置通知:在update方法执行后执行
        @After("pt()")
        public void method1() {
            System.out.println(System.currentTimeMillis());
        }
    }
    

    在这里插入图片描述

  • 环绕通知@Around,aop包下的MyAdvice类代码如下

    package at.guigu.aop;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAdvice {
        // 定义切入点:即定义公共功能在哪执行
        //此处代表在update()方法处执行
        @Pointcut("execution(void at.guigu.service.BrandService.update())")
        private void pt(){}
        // 环绕通知方法
        @Around("pt()")
        public Object method1(ProceedingJoinPoint pjp) throws Throwable {
            // 获取包含原始方法(即目标方法)签名信息的对象
            Signature signature = pjp.getSignature();
            // 获取原始方法(即目标方法)名称
            String name = signature.getName();
            // 获取原始方法(即目标方法)的完整签名
            String string = signature.toString();
            // 获取原始方法(即目标方法)所在类的Class对象
            Class declaringType = signature.getDeclaringType();
            // 获取始方法(即目标方法)所在类的全限定名
            String declaringTypeName = signature.getDeclaringTypeName();
            System.out.println(name);
            System.out.println(string);
            System.out.println(declaringType);
            System.out.println(declaringTypeName);
            // 目标方法前的自定义逻辑
            System.out.println("前:" + System.currentTimeMillis());
            // 调用原始操作(即目标方法)
            Object ret = pjp.proceed();
            // 目标方法后的自定义逻辑
            System.out.println("后:" + System.currentTimeMillis());
            return ret;
        }
    }
    

    在这里插入图片描述

    注意:

    1.在该通知类中抛出异常是因为不知道目标是否有异常,所以在调用原始操作时会强制抛出异常

    2.获取原始方法(即目标方法)所在类的Class对象时,若原始方法是接口中的则最终显示的是Interface而不是Class,如上图所示

  • 抛出异常后通知@AfterThrowing,aop包下的MyAdvice类代码如下

    package at.guigu.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAdvice {
    
        // 定义切入点:即定义公共功能在哪执行
        //此处代表在update()方法处执行
        @Pointcut("execution(void at.guigu.service.BrandService.update())")
        private void pt(){}
    
        // 通知类
        // 抛出异常后通知
        @AfterThrowing("pt()")
        public void method1() {
            System.out.println(System.currentTimeMillis());
        }
    }
    
    • BrandServiceImpl类代码如下(存在异常)

      package at.guigu.service.impl;
      
      import at.guigu.service.BrandService;
      import org.springframework.stereotype.Service;
      
      @Service("bransServiceImpl")
      public class BrandServiceImpl implements BrandService {
          @Override
          public void save() {
              System.out.println(System.currentTimeMillis());
              System.out.println("bookDao save...");
          }
          @Override
          public void update() {
              int i = 1/0;
              System.out.println("bookDao update...");
          }
      }
      

      此时运行截图如下

      在这里插入图片描述

    • BrandServiceImpl类代码如下(不存在异常)

      package at.guigu.service.impl;
      
      import at.guigu.service.BrandService;
      import org.springframework.stereotype.Service;
      
      @Service("bransServiceImpl")
      public class BrandServiceImpl implements BrandService {
          @Override
          public void save() {
              System.out.println(System.currentTimeMillis());
              System.out.println("bookDao save...");
          }
          @Override
          public void update() {
              System.out.println("bookDao update...");
          }
      }
      

      此时运行截图如下

      在这里插入图片描述

案例1:测量业务层接口万次执行效率(基于注解形式)

需求:任意业务层接口执行均可显示其执行效率(执行时长)

分析:

​ 业务功能:业务接口执行前后分别记录时间,求差值得到执行效率

​ 通知类型选择前后均可增强的类型——环绕通知

注意:本案例以Spring集成MyBatis部分的示例为例,并集成JUnit进行测试,重复步骤可见Spring集成MyBatis部分代码示例,此处只写需要添加代码或未重复部分的步骤示例

  • Step1: 导入坐标

    • 导入Spring基础坐标:spring-context

    • 导入Spring提供的监听器ContextLoaderListener的相关坐标:spring-web

    • 导入Spring集成Web环境相关坐标:servlet、jsp

    • 导入Spring注解相关坐标:Annotation

    • 导入与AOP相关的坐标:aop、aspectj

      • AOP坐标会在导入spring-context坐标后系统自动导入,如图所示

        在这里插入图片描述

    • 导入数据库相关坐标:mysql、数据源坐标(druid、cp30)

    • 导入Spring集成JUnit相关坐标:junit、spring-test

    • 导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring

  • Step2: 在Spring的核心配置类SpringConfiguration中添加@EnableAspectJAutoProxy注解,完整代码如下

    package at.guigu.config;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.context.annotation.Import;
    
    // 该注解代表该类是Spring的核心配置类
    @Configuration
    // 配置注解的组件扫描<context:component-scan base-package="at.guigu"></context:component-scan>
    @ComponentScan("at.guigu")
    @MapperScan("at.guigu.dao")
    // 引入拆分配置文件<import resource="applicationContext-xxx.xml"/>
    @Import({DataSourceConfiguration.class, MyBatisConfiguration.class})
    @EnableAspectJAutoProxy
    public class SpringConfiguration {
    }
    
  • Step3: 创建一个与三层架构包同级的aop包,并在该包下创建MyAdvice通知类,代码如下

    • Step3-1: 在该类中创建一个通知(即方法),并写入作为公共功能的内容
    • Step3-2: 在该类中用@Pointcut定义切入点
      • 切入点的定义依托一个不具有实际意义的方法(即无参数、无返回值,方法体内无实际逻辑)
    • Step3-3: 绑定切入点与通知的关系,并指定通知的执行位置。(即定义切面)
    • Step3-4: 为通知类添加@Component以及@Aspect注解
      • @Component:将切面类作为Spring容器中的一个Bean来管理
      • @Aspect:标记该类为切面类
    • Step3-5: 通过ProceedingJoinPoint接口中的getSignature方法获取原始方法签名信息对象,并调用该对象中的getName方法来获取当前原始方法的名称,以此来判断业务层中不同方法的万次执行效率
    package at.guigu.aop;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAdvice {
    
        // 匹配任意返回类型,at包下的guigu包下的任意子包下的以Service为结尾的类或接口中的任意数量参数的任意方法
        // 即匹配业务层下的所有方法
        @Pointcut("execution(* at.guigu.*.*Service.*(..))")
        private void servicePt(){}
    
        // 环绕通知方法
        @Around("servicePt()")
        public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
            // 获取包含原始方法(即目标方法)签名信息的对象,包括方法名、声明类型、参数类型等。
            Signature signature = pjp.getSignature();
            // 获取方法名称
            String name = signature.getName();
            System.out.println(name);
            
            long start = System.currentTimeMillis();
            for (int i = 0; i < 10000; i++) {
                Object ret = pjp.proceed();
            }
            long end = System.currentTimeMillis();
            System.out.println(name + "执行一万遍的效率为:" + (end - start) + "ms");
        }
    }
    
  • Step4: 在test包下创建三层架构包的业务层service包,并在该包中创建BrandServiceTest测试类,完整代码如下

    • Step4-1: 该类要使用@Runwith注解替换原来的运行器,并设置新的类运行器
      • 其属性为SpringRunner.classSpringJUnit4ClassRunner.class:用于集成 Spring 测试框架
    • Step4-2: 该类使用@ContextConfiguration指定Spring配置文件或Spring配置类
      • 指定Spring配置文件:@ContextConfiguration("classpath:applicationContext.xml")
      • 指定单个Spring配置类:@ContextConfiguration(classes = SpringConfiguration.class)
      • 指定多个Spring配置类:@ContextConfiguration(classes = {SpringConfiguration.class,...})
    package at.guigu.service;
    
    import at.guigu.config.SpringConfiguration;
    import at.guigu.pojo.Brand;
    import org.junit.Test;
    import org.junit.runner.RunWith;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.test.context.ContextConfiguration;
    import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
    
    import java.util.List;
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfiguration.class)
    public class BrandServiceTest {
        @Autowired
        private BrandService brandService;
    
        @Test
        public void testGetAll( ) {
            List<Brand> brands = brandService.getAll();
            System.out.println(brands);
        }
        @Test
        public void testGetById() {
            Brand brand = brandService.getById(1);
        }
    }
    

    在这里插入图片描述

AOP通知获取数据(基于注解形式)

  • AOP通知获取的数据主要有三种

    • 获取切入点方法的参数

      • 环绕通知利用ProceedingJoinPoint接口获取

        ProceedingJoinPoint接口是JoinPoint接口的子接口,所以在环绕通知中直接用ProceedingJoinPoint调用getArgs()即可

      • 其它四种通知类型利用JoinPoint接口获取

        JoinPoint接口解释
        Object[] getArgs()获取原始方法(即目标方法)执行时接收的实参值
    • 获取切入点方法的返回值

      • 环绕通知
        • 利用ProceedingjoinPoint接口中的proceed()方法即可。此处不在做演示
      • 返回后通知可以获取
    • 获取切入点方法的异常

      • 环绕通知
      • 抛出异常后通知

获取切入点方法的参数步骤

  • 获取切入点方法的参数——其它四种通知类型利用JoinPoint接口获取 (此处仅以前置通知为例,其余三种一样)

    • 给业务层中的update方法添加参数,以供测试,代码截图如下

      在这里插入图片描述

    • 通知类MyAdvice代码如下

      package at.guigu.aop;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Before;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.stereotype.Component;
      
      import java.util.Arrays;
      
      @Aspect
      @Component
      public class MyAdvice {
      
          // 定义切入点:即定义公共功能在哪执行
          //此处代表在update()方法处执行
          @Pointcut("execution(void at.guigu.service.BrandService.update(..))")
          private void pt(){}
      
          // 通知类
          // 前置通知:在update方法执行前执行
          @Before("pt()")
          public void method1(JoinPoint jp) {
              // 获取原始方法(即目标方法)执行时接收的实参值
              Object[] args = jp.getArgs();
              // 将其转化为字符串数组
              System.out.println(Arrays.toString(args));
              System.out.println(System.currentTimeMillis());
          }
      }
      

      在这里插入图片描述

  • 获取切入点方法的参数——环绕通知类型利用ProceedingJoinPoint接口获取

    • 给业务层中的update方法添加参数,以供测试,代码截图如下

      由图可看出原始方法接收的实参值为1zhangsan

      在这里插入图片描述

    • 通知类MyAdvice代码如下

      在获取到实参值后可对实参值进行处理后作为参数传入proceed(Object[] var1)方法中修改原始方法的参数值并执行原始方法

      package at.guigu.aop;
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.stereotype.Component;
      
      import java.util.Arrays;
      
      @Aspect
      @Component
      public class MyAdvice {
          // 定义切入点:即定义公共功能在哪执行
          //此处代表在update()方法处执行
          @Pointcut("execution(void at.guigu.service.BrandService.update(..))")
          private void pt(){}
          // 环绕通知方法
          @Around("pt()")
          public Object method1(ProceedingJoinPoint pjp) throws Throwable {
              // 获取原始方法执行时接收的实参值
              Object[] args = pjp.getArgs();
              System.out.println("原始方法执行时接收的参数值为:" + Arrays.toString(args));
              // 对实参值进行处理
              args[0] = 200;
              // 目标方法前的自定义逻辑
              System.out.println("前:" + System.currentTimeMillis());
              // 调用原始操作(即目标方法)
              Object ret = pjp.proceed(args);
              // 目标方法后的自定义逻辑
              System.out.println("后:" + System.currentTimeMillis());
              return ret;
          }
      }
      

      在这里插入图片描述

获取切入点方法的返回值步骤

  • 注意

    • 仅以返回后通知为例,因为环绕利用ProceedingjoinPoint接口中的proceed()方法即可。此处不在演示环绕通知
  • Step1: 给业务层中的update方法添加参数,以供测试,代码截图如下

    在这里插入图片描述

  • Step2: 通知类MyAdvice代码如下

    • Step2-1: 给返回后通知方法传入一个接收返回值的参数Object[] ret

    • Step2-2: 给返回后通知注解@AfterReturning(value,returning)添加returning属性,其作用是将原始方法的返回值传递给通知方法中的Object[] ret。注意:该属性值名必须与接收返回值的参数名一致,否则报错

    • Step2-3: 若返回后通知方法也要获取原始方法的参数的话,则需要将JoinPoint接口作为参数,且该参数必须在接收返回值参数前面

    • 完整代码如下

      package at.guigu.aop;
      
      import org.aspectj.lang.JoinPoint;
      import org.aspectj.lang.annotation.AfterReturning;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.stereotype.Component;
      
      import java.util.Arrays;
      
      @Aspect
      @Component
      public class MyAdvice {
      
          // 定义切入点:即定义公共功能在哪执行
          //此处代表在update()方法处执行
          @Pointcut("execution(String at.guigu.service.BrandService.update(..))")
          private void pt(){}
      
          // 通知方法
          // 返回后通知:在update方法执行后执行
          @AfterReturning(value = "pt()", returning = "ret")
          public void method1(JoinPoint jp, String ret) {
              // 获取原始方法(即目标方法)执行时接收的实参值
              Object[] args = jp.getArgs();
              // 将其转化为字符串数组
              System.out.println(Arrays.toString(args));
              // 打印原始方法的返回值
              System.out.println(ret);
              System.out.println(System.currentTimeMillis());
          }
      }
      

      在这里插入图片描述

获取切入点方法的异常的步骤

  • 环绕通知: 将原来的异常抛出改为利用try…catch进行异常捕获处理即可,如下图所示

    在这里插入图片描述

  • 抛出异常后通知:

    • Step1: 将业务层中update方法更改为如图所示,变为存在异常,以供测试

      在这里插入图片描述

    • Step2: 通知类MyAdvice代码如下

      • Step2-1: 给返回后通知方法传入一个接收异常的参数Throwable e

      • Step2-2: 给返回后通知注解@AfterReturning(value,returning)添加returning属性,其作用是将异常传递给通知方法中的Throwable e。注意:该属性值名必须与接收异常的参数名一致,否则报错

      package at.guigu.aop;
      
      import org.aspectj.lang.ProceedingJoinPoint;
      import org.aspectj.lang.annotation.AfterThrowing;
      import org.aspectj.lang.annotation.Around;
      import org.aspectj.lang.annotation.Aspect;
      import org.aspectj.lang.annotation.Pointcut;
      import org.springframework.stereotype.Component;
      
      @Aspect
      @Component
      public class MyAdvice {
      
          // 定义切入点:即定义公共功能在哪执行
          //此处代表在update()方法处执行
          @Pointcut("execution(void at.guigu.service.BrandService.update())")
          private void pt(){}
      
          // 通知类
          // 抛出异常后通知
          @AfterThrowing(value = "pt()",throwing = "e")
          public void method1(Throwable e) {
              System.out.println(System.currentTimeMillis());
          }
      }
      

      在这里插入图片描述

Spring事务控制

  • 作用:在数据访问层(即持久层)或业务层中通过PlatformTransactionManager接口(即事务平台管理器)保障一系列的数据库操作要么同时成功要么同时失败

    • Spring内部提供了这个接口的实现类DataSourceTransactionManagerHibernateTransactionManager
  • 事务控制有两种

    • 编程式事务控制
      • 手动编写事务管理代码,使用 PlatformTransactionManager 接口管理事务
    • 声明式事务控制
      • 不需要手动编写事务管理代码,使用注解(如 @Transactional)或 XML 配置声明事务即可
      • 它是不侵入式的,即业务逻辑对象不会意识到正在事务管理之中,若想要改变事务管理策略的话,只需要在定义文件中重新配置即可。
      • 在不需要事务管理时,只要修改配置文件即可移除事务管理服务,无需改变代码重新编译,维护起来更方便
      • 声明式事务控制的本质是AOP完成的,对方法前后进行拦截,在执行原始方法(即目标方法)之前开启事务,在执行完原始方法(即目标方法)之后根据执行情况来提交或回滚事务

编程式事务控制相关对象

  • 编程式事务控制三大对象
    • PlatformTransactionManager接口
    • TransactionDefinition接口
    • TransactionStatus接口
  • 注意
    • 前两个编程式事务控制对象不需要去手动编程,只需要在配置文件中进行配置
    • 最后一个编程式事务控制对象是被动封装事务的状态信息,状态信息会随着程序的运行自动改变,所以不需要去配置

PlatformTransactionManager接口

  • PlatformTransactionManager接口是Spring的事务管理器对象,它提供了我们常用的操作事务的方法

    PlatformTransactionManager接口中的方法解释
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;获取事务状态信息
    void commit(TransactionStatus status) throws TransactionException提交事务
    void rollback(TransactionStatus status) throws TransactionException回归事务
  • 不同的持久层技术使有不同的实现类,比如

    • 若持久层使用的技术是JDBC或MyBatis时,实现类为:org.springframework.jdbc.datasource.DataSourceTransactionManager
    • 若持久层使用的技术是Hibernate时,实现类为:org.springframework.orm.hibernate5.HibernateTransactionManager

TransactionDefinition接口

  • TransactionDefinition接口是事务的定义信息对象,用于定义事务的属性和配置选项,可能用到的方法如下

    TransactionDefinition接口中的方法解释
    default int getPropagationBehavior()获取事务的传播行为,决定了当前事务如何与外部事务交互。默认为0,即若当前没事务则创建一个新事务;若有事务则加入到现有事务中。
    default int getIsolationLevel()获取事务的隔离级别
    default int getTimeout()获取事务的超时时间(单位:秒),如果事务在指定时间内没有完成,Spring 会自动回滚事务。默认值为-1,代表无超时时间限制
    default boolean isReadOnly()事务是否为只读事务,若是则返回true,默认为false。建议查询时设置为true
  • 事务的传播行为有七种

    • REQUIRED(默认值0):若当前有事务,则加入到现有事务中;若当前无事务,则新建一个事务。

    • SUPPORTS(1):若当前有事务则支持当前事务,加入到当前事务;若当前无事务,则以非事务方式执行

    • MANDATORY(2):若当前有事务,则加入到现有事务中;若当前无事务,则抛出异常

    • REQUERS_NEW(3):不论当前是否有事务都会去新建一个事务,若当前存在事务,就会把当前事务挂起。

    • NOT_SUPPORTED(4):不论当前是否有事务都会以非事务方式运行,若当前存在事务,就会把当前事务挂起。

    • NEVER(5):以非事务方式运行,若当前存在事务,就会抛出异常

    • NESTED(6):若当前有事务,则会在当前事务的内部新建一个事务来执行;若当前无事务,则会新建一个事务

      • 注意:内部事务与其外部事务相互独立,均有自己的提交和回滚规则
    • 理解示例如下(以默认值为例,其它类似)

      • 当a业务方法调用b业务方法时,b业务方法会看a是否有事务 ,若a有事务则加入到a事务中;若a无事务则b就会创建一个新事务
  • 事务的隔离级别有四种,主要用于解决事务并发所产生的问题,即脏读、不可重复度(虚读)、幻读

    • 读未提交READ_UNCOMMITTED(1):并发产生的三种问题均不能解决

    • 读已提交READ_COMMITTED(Oracle默认)(2):只能解决脏读问题

    • 可重复读REPEATABLE_READ(MySQL默认)(4):解决脏读、不可重复读问题

    • 串行化SERIALIZABLE(8):并发产生的三种问题均可解决

    • 以上四种隔离级别从小到大,安全性越来越高,但是效率越来越小,所以一般使用MySQL默认的隔离级别即可。

TransactionStatus接口

  • TransactionStatus接口是事务的状态对象,可用于查询当前事务具体的运行状态,可能用到的方法如下

    TransactionStatus接口中的方法解释
    boolean hasSavepoint()检查当前事务是否有保存点(即是否存储的有回滚点)
    boolean isCompleted()检查当前事务是否已被提交或回滚
    boolean isNewTransaction()检查当前事务是否是一个新的事务
    boolean isRollbackOnly()查当前事务是否标记为仅回滚状态
  • 保存点:允许在事务执行过程中设置某个特定的状态,以便在需要时回滚到这个状态。保存点通常用于 嵌套事务 或复杂事务场景中,当某些操作失败时,可以回滚到某个中间状态,而不是回滚整个事务。

快速入门

  • 案例:银行账户间转账业务

在这里插入图片描述

环境准备相同步骤

  • Step1: 导入坐标

    • 导入Spring基础坐标:spring-context

    • 导入Spring提供的监听器ContextLoaderListener的相关坐标:spring-web

    • 导入Spring集成Web环境相关坐标:servlet、jsp

    • 导入Spring注解相关坐标:Annotation

    • 导入与AOP相关的坐标:aop、aspectj

      • AOP坐标会在导入spring-context坐标后系统自动导入,如图所示

        在这里插入图片描述

    • 导入事务相关坐标:spring-tx

    • 导入数据库相关坐标:mysql、数据源坐标(druid、cp30)

    • 导入Spring集成JUnit相关坐标:junit、spring-test

    • 导入Spring集成MyBatis相关坐标:mybatis、spring-jdbc、mybatis-spring

    <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 http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.example</groupId>
            <artifactId>SpringDemo</artifactId>
            <version>1.0-SNAPSHOT</version>
        </parent>
        <artifactId>SpringTranXmlDemo</artifactId>
        <packaging>war</packaging>
        <name>SpringTranDemo Maven Webapp</name>
        <url>http://maven.apache.org</url>
        <dependencies>
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>3.8.1</version>
                <scope>test</scope>
            </dependency>
            <!--===================Spring基础坐标=======================-->
            <!--spring坐标-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>6.1.6</version>
            </dependency>
            <!--===================Spring自带监听器ContextLoaderListener所需坐标=======================-->
            <!--spring-web-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-web</artifactId>
                <version>5.2.25.RELEASE</version>
            </dependency>
    
            <!--===================Spring集成Web环境相关坐标=======================-->
            <!-- servlet-->
            <dependency>
                <groupId>javax.servlet</groupId>
                <artifactId>javax.servlet-api</artifactId>
                <version>4.0.1</version>
                <scope>provided</scope>
            </dependency>
    
            <!--jsp-->
            <dependency>
                <groupId>javax.servlet.jsp</groupId>
                <artifactId>javax.servlet.jsp-api</artifactId>
                <version>2.3.3</version>
                <scope>provided</scope>
            </dependency>
    
            <!--===================Spring注解相关坐标=======================-->
            <!--Annotation坐标-->
            <dependency>
                <groupId>javax.annotation</groupId>
                <artifactId>javax.annotation-api</artifactId>
                <version>1.3.2</version>
            </dependency>
    
            <!--=====================AOP相关坐标=========================-->
            <dependency>
                <groupId>org.aspectj</groupId>
                <artifactId>aspectjweaver</artifactId>
                <version>1.9.22.1</version>
            </dependency>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-tx</artifactId>
                <version>6.1.15</version>
            </dependency>
    
    
            <!--=====================数据库相关坐标=========================-->
            <!--mysql坐标-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.33</version>
            </dependency>
    
            <!--druid坐标-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.2.18</version>
            </dependency>
    
            <!--c3p0坐标-->
            <dependency>
                <groupId>com.mchange</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.5.5</version>
            </dependency>
    
            <!--===================Spring集成junit相关坐标=======================-->
            <!--junit坐标-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
                <scope>test</scope>
            </dependency>
            <!--spring-test坐标-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>6.1.6</version>
                <scope>test</scope>
            </dependency>
    
            <!--=====================MyBatis相关坐标=========================-->
            <!--MyBatis坐标-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis</artifactId>
                <version>3.5.16</version>
            </dependency>
            <!--mybatis-spring-->
            <dependency>
                <groupId>org.mybatis</groupId>
                <artifactId>mybatis-spring</artifactId>
                <version>3.0.3</version>
            </dependency>
            <!--spring-jdbc-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-jdbc</artifactId>
                <version>6.1.10</version>
            </dependency>
        </dependencies>
        <build>
            <finalName>SpringTranDemo</finalName>
            <plugins>
                <!-- Tomcat插件 -->
                <plugin>
                    <groupId>org.apache.tomcat.maven</groupId>
                    <artifactId>tomcat7-maven-plugin</artifactId>
                    <version>2.2</version>
                </plugin>
            </plugins>
        </build>
    </project>
    
  • Step2: 右键源代码配置文件目录(即资源文件resources)→NewFile,创建properties配置文件,博主文件名为jdbc.properties,该配置文件代码如下

    • 注意: properties配置文件中配置的各个属性前必须添加个id.(即id.属性,比如:属性url就设置为id.url,博主设置的为jdbc.url),以供Spring配置文件可以使用属性占位符${} 语法引用这些属性
    #driverClassName代表数据库驱动,后跟驱动全类名(在MySQL驱动jar包下的META-INF下的services文件夹下的java.sql.Driver文件内)
    jdbc.driverClassName=com.mysql.cj.jdbc.Driver
    # 数据库连接URL
    jdbc.url=jdbc:mysql://localhost:3306/test02?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=Asia/Shanghai
    # 数据库用户名
    jdbc.username=root
    # 数据库密码
    jdbc.password=123456
    # 初始化连接数量---即容器中初始的数据库连接数量
    jdbc.initialSize=5
    # 最大活跃连接数量---容器中初始为5个,但若5个用完了,此时可以在申请5个数据库连接数量
    #也就是说容器中最多存放10个数据库连接
    jdbc.maxActive=10
    # 获取连接时的最大等待时间,单位:毫秒。---与数据库进行连接时若超过3s仍未连接成功,则会报错
    jdbc.maxWait=3000
    #最小空闲连接数量---minIdle=5
    # 配置检测连接是否有效的SQL,可以是一个查询语句,如果不指定则默认为"SELECT 1"---validationQuery=SELECT 1
    # 是否开启自动提交事务---defaultAutoCommit=true
    
  • Step3: 创建数据库表account 并使IDEA与数据库建立连接 ,SQL代码如下

    DROP TABLE IF EXISTS account;
    #创建账户表
    CREATE TABLE account (
    	id INT PRIMARY KEY AUTO_INCREMENT,
    	name VARCHAR(10),
    	money DOUBLE(10,2)
    );
    #添加数据
    INSERT INTO account(name, money) VALUES ('张三', 1000), ('李四', 1000);
    SELECT * FROM account;
    

    在这里插入图片描述

基于XML的声明式事务控制

环境准备
  • Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类User,代码所如下

    package at.guigu.pojo;
    
    public class User {
        private String name;
        private double money;
        public User() {
    
        }
        public User(String name, double money) {
            this.name = name;
            this.money = money;
        }
    
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    
        public double getMoney() {
            return money;
        }
        public void setMoney(double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
  • Step2: 右键源代码配置文件目录(即资源文件resources)→NewFile,创建MyBatis核心配置文件(名为mybatis-config.xml),代码如下:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "https://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
    
        <!--设置别名-->
        <typeAliases>
            <package name="at.guigu.pojo"/>
        </typeAliases>
    
    </configuration>
    
  • Step3: 右键源代码配置文件目录(即资源文件resources)→NewXML Configuration FileSpring Config,创建Spring核心配置文件(名为applicationContext.xml),默认代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    </beans>
    
    • Step3-1: 使用context命名空间加载 jdbc.properties 文件(前提:需引入context命名空间和约束路径)
      • context命名空间:xmlns:context="http://www.springframework.org/schema/context"
      • context约束路径:http://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsd
    • Step3-2: 配置数据源对应的bean
    • Step3-3: 配置MyBatis的SqlSessionFactory
      • 配置数据源
      • 配置MyBatis核心配置文件(注意:若有的配置必须通过MyBatis核心配置文件配置时,则需要该步)
      • 配置别名
    • Step3-4: 引入dao包下所有接口对应的SQL映射文件
      • 此时Spring会进行持久层扫描,自动生成该层中对应接口的bean
    • Step3-5: 引入tx命名空间和约束路径(作用:用来配置平台事务管理器以及事务增强)
      • tx命名空间:xmlns:tx="http://www.springframework.org/schema/tx"
      • tx约束路径:http://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsd
    • Step3-6: 配置平台事务管理器
      • 由于持久层此时使用的技术是JDBC或MyBatis时,所以实现类为:org.springframework.jdbc.datasource.DataSourceTransactionManager
    • Step3-7: 配置通知:声明式事务的增强
    <?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:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!--使用`context`命名空间加载 `properties` 文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <!--Druid对应的bean-->
        <bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource">
            <!--使用属性占位符`${}`语法引用properties文件中的属性-->
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        <!--配置MyBatis的SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--配置数据源-->
            <property name="dataSource" ref="dataSourceDruid"/>
            <!--加载MyBatis的核心配置文件-->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <!--配置别名-->
            <property name="typeAliasesPackage" value="at.guigu.pojo"/>
        </bean>
        <!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的bean-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="at.guigu.dao"/>
        </bean>
    
        <!--配置平台事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSourceDruid"/>
        </bean>
    
        <!--配置通知:声明式事务的增强-->
        <!--transaction-manager属性值为对应平台事务管理器的bean的id-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <tx:attributes>
                <tx:method name="*"/>
            </tx:attributes>
        </tx:advice>
    
    </beans>
    
  • Step3: 创建三层架构包,且初始代码分别如下

    • 在持久层dao包下创建UserDao接口

      package at.guigu.dao;
      
      public interface UserDao {
      }
      
    • 在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与UserDao接口对应的目录下创建 SQL映射文件UserDao.xml ,如图所示,SQL映射文件代码如下所示

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <!--namespace:名称空间-->
      <mapper namespace="at.guigu.dao.UserDao">
          <!--结果映射-->
          <resultMap id="userResultMap" type="user">
              <!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可-->
              <result column="name" property="name"/>
              <result column="money" property="money"/>
          </resultMap>
      </mapper>
      
    • 在业务层service包下创建UserService类,初始代码如下

      package at.guigu.service;
      
      import at.guigu.dao.UserDao;
      
      public class UserService {
          private UserDao userDao;
          
          public void setUserDao(UserDao userDao) {
              this.userDao = userDao;
          }
      }
      
      

      注意:此处使用的是setter方法注入,所以必须在业务层中用setter方法将IOC容器中自动生成的持久层接口对应的bean注入到业务层对应的类中

    • 在表现层web包下创建BrandServlet类,初始代码如下

      package at.guigu.web;
      
      import javax.servlet.ServletException;
      import javax.servlet.http.HttpServlet;
      import javax.servlet.http.HttpServletRequest;
      import javax.servlet.http.HttpServletResponse;
      import java.io.IOException;
      
      public class UserServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
          }
      
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doGet(request, response);
          }
      }
      
  • Step4: 在web项目核心目录(即WEB-INF)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener 监听器、web配置。完整代码如下

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <!--全局初始化参数-->
      <context-param>
        <!--定义参数的名称,必须是唯一的-->
        <param-name>contextConfigLocation</param-name>
        <!--定义参数的值-->
        <param-value>classpath:applicationContext.xml</param-value>
      </context-param>
    
      <!--监听器-->
      <!--配置Spring所提供的`ContextLoaderListener` 监听器-->
      <listener>
        <!--监听器类的全限定名-->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <!--声明一个Servlet-->
      <servlet>
        <!--声明的Servlet的类名-->
        <servlet-name>UserServlet</servlet-name>
        <!--声明的Servlet的全限定名-->
        <servlet-class>at.guigu.web.UserServlet</servlet-class>
      </servlet>
    
      <!--将URL模式映射到特定的Servlet上(即UserServlet)-->
      <servlet-mapping>
        <!--指定的Servlet的类名-->
        <servlet-name>UserServlet</servlet-name>
        <!--给指定的Servlet设置url,相当于@WebServlet("/userServlet")-->
        <url-pattern>/userServlet</url-pattern>
      </servlet-mapping>
    </web-app>
    
    
银行账户间转账业务
  • Step1: 在dao包下的UserDao接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句

    • UserDao接口代码如下

      package at.guigu.dao;
      
      import at.guigu.pojo.User;
      import org.apache.ibatis.annotations.Param;
      import java.util.List;
      
      public interface UserDao {
      
          void inMoney(@Param("name")String name, @Param("money")Double money);
      
          void outMoney(@Param("name")String name, @Param("money")Double money);
      
          List<User> all();
      }
      

      注意:定义含多个参数的接口方法时,要通过@Param("参数")注解来将对应的方法参数放到SQL映射文件中SQL语句对应的参数占位符上,其中注解参数名称要和SQL映射文件中SQL语句中的参数占位符名称对应一致

    • SQL映射文件UserDao.xml文件代码如下

      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE mapper
              PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
              "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
      
      <!--namespace:名称空间-->
      <mapper namespace="at.guigu.dao.UserDao">
          <!--结果映射-->
          <resultMap id="userResultMap" type="user">
              <!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可-->
              <result column="name" property="name"/>
              <result column="money" property="money"/>
          </resultMap>
      
          <update id="inMoney">
              update account set money = money + #{money} where name = #{name};
          </update>
          <update id="outMoney">
              update account set money = money - #{money} where name = #{name};
          </update>
          <select id="all" resultMap="userResultMap">
              select * from account;
          </select>
      </mapper>
      
  • Step2: 在业务层service包下的UserService类来调用dao包下的UserDao接口中的方法,代码如下

    package at.guigu.service;
    
    import at.guigu.dao.UserDao;
    import at.guigu.pojo.User;
    
    import java.util.List;
    
    public class UserService {
        private UserDao userDao;
        public void setUserDao(UserDao userDao) {
            this.userDao = userDao;
        }
        public void inMoney(String name, Double money) {
            userDao.inMoney(name, money);
        }
        public void outMoney(String name, Double money) {
            userDao.outMoney(name, money);
        }
        public void transfer(String out, String in, Double money) {
            this.outMoney(out, money);
            this.inMoney(in, money);
        }
        public List<User> getAll() {
            return userDao.all();
        }
    }
    
  • Step3: 在Spring的核心配置文件中配置业务层对应的bean,并将持久层对应的bean依赖注入到该业务层中;同时配置事务的aop织入,完整代码如下

    • Step3-1: 在Spring的核心配置文件中引入AOP的命名空间:xmlns:aop="http://www.springframework.org/schema/aop"
    • Step3-2: 在Spring的核心配置文件中引入AOP的约束路径:http://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd
    • Step3-3: 配置业务层bean(该示例并未创建持久层,所以不用配置持久层bean)
    • Step3-4: 配置切面类(即通知类)对应的bean
      • 此处为声明式事务的增强,已在环境准备中完成该步
    • Step3-5: 配置织入关系:将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上
      • 配置普通增强使用<aop:aspect>标签,可详见AOP部分内容
      • 配置事务的增强使用<aop:advisor>标签
    <?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:tx="http://www.springframework.org/schema/tx"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="
           http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd
           http://www.springframework.org/schema/tx
           http://www.springframework.org/schema/tx/spring-tx.xsd
           http://www.springframework.org/schema/aop
           http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!--使用`context`命名空间加载 `properties` 文件-->
        <context:property-placeholder location="classpath:jdbc.properties"/>
    
        <!--Druid对应的bean-->
        <bean id="dataSourceDruid" class="com.alibaba.druid.pool.DruidDataSource">
            <!--使用属性占位符`${}`语法引用properties文件中的属性-->
            <property name="driverClassName" value="${jdbc.driverClassName}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        <!--配置MyBatis的SqlSessionFactory-->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--配置数据源-->
            <property name="dataSource" ref="dataSourceDruid"/>
            <!--加载MyBatis的核心配置文件-->
            <property name="configLocation" value="classpath:mybatis-config.xml"/>
            <!--配置别名-->
            <property name="typeAliasesPackage" value="at.guigu.pojo"/>
        </bean>
        <!--引入dao包下所有接口对应的SQL映射文件即MyBatis 持久层扫描,会自动生成该层中对应接口的bean-->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="basePackage" value="at.guigu.dao"/>
        </bean>
    
        <!--配置平台事务管理器-->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSourceDruid"/>
        </bean>
    
        <!--配置UserService实现类bean  即目标对象-->
        <bean id="userService" class="at.guigu.service.UserService">
            <property name="userDao" ref="userDao"/>
        </bean>
    
        <!--配置通知类(即切面类)对应的bean:声明式事务的增强-->
        <!--transaction-manager属性值为对应平台事务管理器的bean的id-->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!--配置事务属性-->
            <tx:attributes>
                <!--name属性指定需要增强的原始方法-->
                <tx:method name="*"/>
            </tx:attributes>
        </tx:advice>
    
        <!--配置织入关系:即将对应通知方法(即增强方法)应用到对应目标对象的对应原始方法上-->
        <aop:config>
            <!--配置指定切面(切点+通知)方式一-->
                <!--配置切入点-->
            <aop:pointcut id="myPointcut" expression="execution(* at.guigu.service.*.*(..))"/>
                <!--配置用纸-->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"/>
            <!--
            配置指定切面(切点+通知)方式二
            <aop:advisor advice-ref="txAdvice" pointcut="execution(* at.guigu.service.*.*(..))"/>
            -->
        </aop:config>
    
    </beans>
    

    配置指定切面的两种方式可详见切入点表达式的抽取的内容

  • Step4: 在表现层web包下的BrandServlet类中来调用业务层service包中的BrandService类中的方法,代码如下:

    package at.guigu.web;
    
    import at.guigu.pojo.User;
    import at.guigu.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import javax.servlet.ServletContext;
    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.List;
    
    public class UserServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 1 获取最大域ServletContext对象
            ServletContext servletContext = request.getServletContext();
            //等同于ServletContext servletContex t = request.getSession().getServletContext();
    
            // 2 获取应用上下文对象(即IOC容器)
            ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    
            //3 获取bean
            UserService userService = app.getBean(UserService.class);
    
            //4 调用方法执行SQL语句
            userService.transfer("张三", "李四", 500.00);
    
            List<User> users = userService.getAll();
            for (User user : users) {
                System.out.println(user);
            }
    
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    
  • Tomcat运行截图如下

    • 无异常时,操作全部执行。张三、李四分别由原来的1000变为了500、1500,如图所示

      在这里插入图片描述

    • 此时若将业务层的代码中添加一个异常则操作就会全不执行,运行截图如下

      在这里插入图片描述

基于注解的声明式事务控制

环境准备
  • Step1: 创建一个与三层架构包同级的pojo包,并在该包下创建实体类User,代码所如下

    package at.guigu.pojo;
    
    import org.apache.ibatis.type.Alias;
    
    @Alias("user")
    public class User {
        private String name;
        private double money;
        public User() {
    
        }
        public User(String name, double money) {
            this.name = name;
            this.money = money;
        }
    
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    
        public double getMoney() {
            return money;
        }
        public void setMoney(double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
  • Step2: 创建一个与三层架构包同级的config包,并在该包下创建拆分配置文件对应的数据源拆分类DataSourceConfiguration,代码如下(以Druid为例)

    • Step2-1: 创建数据源bean

    • Step2-1: 事务管理器配置

    package at.guigu.config;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.PlatformTransactionManager;
    
    import javax.sql.DataSource;
    
    // 分配置文件对应的类不用配置@Configuration以及@ComponentScan注解
    // 加载properties配置文件<context:property-placeholder location="classpath:jdbc.properties"/>
    @PropertySource("classpath:jdbc.properties")
    public class DataSourceConfiguration {
        @Value("${jdbc.driverClassName}")
        private String driverClassName;
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.username}")
        private String username;
        @Value("${jdbc.password}")
        private String password;
    
        /**
         * Druid对应的bean
         * Spring会将当前方法的返回值以指定的id存储到Spring的IOC容器中
         * @return
         * @throws Exception
         */
        @Bean("dataSourceDruid")
        public DataSource getDruidDataSource() throws Exception{
            // 创建数据源对象
            DruidDataSource dataSource = new DruidDataSource();
            // 设置数据源基本连接数据
            dataSource.setDriverClassName(driverClassName);
            dataSource.setUrl(url);
            dataSource.setUsername(username);
            dataSource.setPassword(password);
            return dataSource;
        }
    
        // 事务管理器配置
        @Bean
        public PlatformTransactionManager transactionManager(@Qualifier("dataSourceDruid") DataSource dataSource) {
            DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
            transactionManager.setDataSource(dataSource);
            return transactionManager;
        }
    }
    

    注意:

    • 在以上代码示例中,transactionManager方法通过参数进行bean的依赖注入时,加上了@Qualifier("dataSourceDruid")注解是为了让其精确匹配,此处可以不加,因为此时IOC容器中只有一个DataSource对应的bean,系统会自动去IOC容器中判断是否存在DataSource对应的bean,若有则直接将其作为参数注入。当IOC容器中不只有一个DataSource对应的bean时,此时就需要@Qualifier注解来指明参数注入的是哪个数据源bean
    • @bean注解未显式指定数据源名称时,其在IOC容器中的唯一标识为方法名,即getDruidDataSource
    • 事务管理器配置中注入的数据源bean必须与MyBatisConfiguration配置类中注入的数据源bean是同一一个
  • Step3: 在config包下创建拆分配置文件MyBatis对应的拆分类MyBatisConfiguration,代码如下

    package at.guigu.config;
    
    import org.mybatis.spring.SqlSessionFactoryBean;
    import org.mybatis.spring.mapper.MapperScannerConfigurer;
    import org.springframework.context.annotation.Bean;
    
    import javax.sql.DataSource;
    
    public class MyBatisConfiguration {
        
        @Bean
        public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            // 相当于设置别名<package name="at.guigu.pojo"/>
            sqlSessionFactoryBean.setTypeAliasesPackage("at.guigu.pojo");
            // 相当于配置数据库连接信息
            sqlSessionFactoryBean.setDataSource(dataSource);
            return sqlSessionFactoryBean;
        }
        // 映射扫描配置类,相当于引入dao包下所有接口对应的SQL映射文件<package name="at.guigu.dao"/>
        @Bean
        public MapperScannerConfigurer mapperScannerConfigurer() {
            MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer();
            mapperScannerConfigurer.setBasePackage("at.guigu.dao");
            return mapperScannerConfigurer;
        }
    }
    
  • Step4: 在config包下创建Spring主配置文件对应的主类SpringConfiguration,并引入分配置文件对应的拆分类DataSourceConfiguration以及MyBatisConfiguration,代码如下

    • Step4-1: 启用Spring基于AspectJ 注解驱动的AOP功能——在Spring的核心配置类中添加@EnableAspectJAutoProxy注解

    • Step4-2: 开启注解式事务驱动——在Spring的核心配置类中添加@EnableTransactionManagement注解

    package at.guigu.config;
    
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    import org.springframework.context.annotation.Import;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    // 该注解代表该类是Spring的核心配置类
    @Configuration
    // 配置注解的组件扫描<context:component-scan base-package="at.guigu"></context:component-scan>
    @ComponentScan("at.guigu")
    @MapperScan("at.guigu.dao")
    // 引入拆分配置文件<import resource="applicationContext-xxx.xml"/>
    @Import({DataSourceConfiguration.class, MyBatisConfiguration.class})
    // 启用Spring基于AspectJ 注解驱动的AOP功能
    @EnableAspectJAutoProxy
    // 开启注解式事务驱动
    @EnableTransactionManagement
    public class SpringConfiguration {
    }
    
  • Step5: 创建三层架构包,且初始代码分别如下

    • 在持久层dao包下创建UserDao接口
    package at.guigu.dao;
    
    import org.apache.ibatis.annotations.Mapper;
    
    import java.util.List;
    @Mapper
    public interface UserDao {
        
    }
    

    @Mapper注解作用: 用于标记单个Mapper接口,让MyBatis生成它的实现类并注入到Spring容器中。也就是说此时不需要创建持久层的实现类,有IOC容器自动创建,其唯一标识id为对应接口名首字母大写(即userDao

    该注解也可以使用@MapperScan(at.guigu.dao)来代替,不过该注解需写在Spring的核心配置文件中,表示: 用于扫描指定包中的所有接口,将它们自动注册为Spring的Bean并作为Mapper

    以上两个注解在做项目时可根据实际情况选择

    • 在该Maven项目的源代码配置文件目录(即main包下的resources目录下)创建多级目录,然后在与UserDao接口对应的目录下创建 SQL映射文件UserDao.xml ,如图所示,SQL映射文件代码如下所示
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <!--namespace:名称空间-->
    <mapper namespace="at.guigu.dao.UserDao">
        <!--结果映射-->
        <resultMap id="brandResultMap" type="user">
            <!--由于id为主键,且数据库中的字段名和对应结果映射的目标类中的属性名一样,所以此处不需要主键映射,只需进行非主键映射即可-->
            <result column="name" property="name"/>
            <result column="name" property="name"/>
        </resultMap>
    </mapper>
    

    注意:在MyBatis配置类的形式中,sqlSessionFactoryBean.setTypeAliasesPackage()方法设置别名无效,目前还未知原因,所以结果映射中的type="user"会标红报错,所以解决办法为:利用@Alias("别名")注解为类的全类名设置类型别名

    • 在业务层service包下创建UserService类,初始代码如下

      package at.guigu.service;
      
      import at.guigu.dao.UserDao;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      
      @Service("userService")
      public class UserService {
          @Autowired
          private UserDao userDao;
      }
      
    • 在表现层web包下创建UserServlet类,初始代码如下

      package at.guigu.web;
      
      import javax.servlet.*;
      import javax.servlet.http.*;
      import java.io.IOException;
      
      public class UserServlet extends HttpServlet {
          @Override
          protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
      
          }
          @Override
          protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
              this.doGet(request, response);
          }
      }
      
  • Step4: 在web项目核心目录(即WEB-INF)下的web.xml文件中进行:全局初始化参数、配置Spring所提供的ContextLoaderListener 监听器、web配置。完整代码如下

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <!--配置Spring配置类的全局初始化参数-->
      <context-param>
        <param-name>contextClass</param-name>
        <param-value>org.springframework.web.context.support.AnnotationConfigWebApplicationContext</param-value>
      </context-param>
      <context-param>
        <!--定义参数的名称,必须是唯一的-->
        <param-name>contextConfigLocation</param-name>
        <!--定义参数的值-->
        <param-value>at.guigu.config.SpringConfiguration</param-value>
      </context-param>
    
      <!--监听器-->
      <!--配置Spring所提供的`ContextLoaderListener` 监听器-->
      <listener>
        <!--监听器类的全限定名-->
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
      </listener>
    
      <!--声明一个Servlet-->
      <servlet>
        <!--声明的Servlet的类名-->
        <servlet-name>UserServlet</servlet-name>
        <!--声明的Servlet的全限定名-->
        <servlet-class>at.guigu.web.UserServlet</servlet-class>
      </servlet>
    
      <!--将URL模式映射到特定的Servlet上(即UserServlet)-->
      <servlet-mapping>
        <!--指定的Servlet的类名-->
        <servlet-name>UserServlet</servlet-name>
        <!--给指定的Servlet设置url,相当于@WebServlet("/userServlet")-->
        <url-pattern>/userServlet</url-pattern>
      </servlet-mapping>
    </web-app>
    
银行账户间转账业务
  • Step1: 在dao包下的UserDao接口中写入查询方法,然后在对应的SQL映射文件中写入对应SQL语句

    注意:简单查询SQL语句采用注解方式,复杂SQL语句采用映射文件方式(具体操作可详见Spring集成MyBatis部分内容)

    • UserDao接口代码如下

      package at.guigu.dao;
      
      import at.guigu.pojo.User;
      import org.apache.ibatis.annotations.Mapper;
      import org.apache.ibatis.annotations.Param;
      import org.apache.ibatis.annotations.Select;
      import org.apache.ibatis.annotations.Update;
      
      import java.util.List;
      
      @Mapper
      public interface UserDao {
          @Update("update account set money = money + #{money} where name = #{name}")
          void inMoney(@Param("name") String name, @Param("money") Double money);
      
          @Update("update account set money = money - #{money} where name = #{name}")
          void outMoney(@Param("name") String name, @Param("money") Double money);
      
          @Select("select * from account")
          List<User> all();
      }
      

      注意:定义含多个参数的接口方法时,要通过@Param("参数")注解来将对应的方法参数放到SQL映射文件中SQL语句对应的参数占位符上,其中注解参数名称要和SQL映射文件中SQL语句中的参数占位符名称对应一致

  • Step2: 在业务层service包下的UserService类来调用dao包下的UserDao接口中的方法,代码如下

    • Step2-1: 创建transfer方法来完成账户转账操作,同时该方法要加上@Transactional注解来开启事务(注意:若业务层为接口和实现类,则将该注解添加到接口中对应的方法上,由于博主在业务层使用的直接是类,无接口,所以加在了类中的方法上)
    package at.guigu.service;
    
    import at.guigu.dao.UserDao;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    @Service("userService")
    public class UserService {
        @Autowired
        private UserDao userDao;
    
        @Transactional
        public void transfer(String out, String in, double money) {
            userDao.outMoney(out, money);
            userDao.inMoney(in, money);
        }
    }
    
  • Step3: 在表现层web包下的BrandServlet类中来调用业务层service包中的UserService类中的方法,代码如下

    package at.guigu.web;
    
    import at.guigu.service.UserService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.web.context.support.WebApplicationContextUtils;
    
    import javax.servlet.*;
    import javax.servlet.http.*;
    import java.io.IOException;
    
    public class UserServlet extends HttpServlet {
        @Override
        protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            // 1 获取最大域ServletContext对象
            ServletContext servletContext = request.getServletContext();
            //等同于ServletContext servletContext = request.getSession().getServletContext();
    
            // 2 获取应用上下文对象(即IOC容器)
            ApplicationContext app = WebApplicationContextUtils.getWebApplicationContext(servletContext);
    
            //3 获取bean
            UserService userService = app.getBean(UserService.class);
            // 等同于UserService userService = (UserService) app.getBean("userService");
    
            //4 调用方法执行SQL语句
            userService.transfer("张三", "李四", 500);
    
        }
    
        @Override
        protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            this.doGet(request, response);
        }
    }
    
  • Tomcat运行截图如下

    • 无异常时,操作全部执行。张三、李四分别由原来的1000变为了1100、900,如图所示

      在这里插入图片描述

    • 此时若将业务层的代码中添加一个异常则操作就会全不执行,运行截图如下

      在这里插入图片描述

  • 注意

    • Spring注解式事务(即@Transactional)通常添加在业务层接口中而不会添加到业务层实现类中,以此来降低耦合。
    • 注解式事务可以添加到业务方法上表示当前方法开启事务,也可以添加到接口上表示当前接口所有方法开启事务

Spring事务角色

在这里插入图片描述

  • 事务管理员:是发起事务方,在Spring中指业务层开启事务的方法
  • 事务协调员:是加入事务方,在Spring中指持久层的方法,也可以是业务层的方法

Spring事务相关配置

Spring配置文件形式

标签解释
<tx:advice id transaction-manager>配置事务通知(即声明式事务的增强)。id为事务通知的唯一标识transaction-manager属性值为事务管理器对应bean的唯一标识
<tx:advice>的内嵌标签解释
<tx:attributes>配置事务属性。代替了TransactionDefinition接口的作用
<tx:attributes>的内嵌标签解释
<tx:method name propagation isolation read-only timeout rollback-for no-rollback-for>配置切入点方法的事务参数
<tx:method>的属性解释
name指定需要应用事务管理的方法的名称( 即切入点方法的名称 ),可以使用通配符。*代表匹配任意字符;..代表匹配任意数量的参数。例如:save* 匹配所有以 “save” 开头的方法;*Service.*(..) 匹配所有 *Service 类中接受任意参数的方
propagation控制事务的传播行为,即当前事务如何与外部事务交互。传播行为共有7种,分别为:REQUIRED(默认)、REQUIRES_NEWSUPPORTSNOT_SUPPORTEDMANDATORYNEVERNESTED
isolation定义事务的隔离级别,控制不同事务之间如何隔离。共有四种:READ_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE。MySQL默认为REPEATABLE_READ;Oracle默认为READ_COMMITTED
read-only事务是否为只读事务,若是则返回true,默认为false。建议查询时设置为true
timeout事务的超时时间(单位:秒),如果事务在指定时间内没有完成,Spring 会自动回滚事务。默认没有超时限制。
rollback-for指定哪些异常应该触发事务回滚。可以指定多个异常类,多个异常类之间使用逗号分隔。比如:rollback-for="SQLException,IOException"
no-rollback-forrollback-for 相对,指定哪些异常不会触发事务回滚

Spring注解形式

注解解释
@Mapper用在持久层接口上,代表由Spring自动生成持久层接口对应的bean
@MapperScan("at.guigu.dao")代理@Mapper注解,用在Spring核心配置类上。代表由Spring自动生成持久层接口对应的bean
@EnableTransactionManagement用于在Spring核心配置类中启用 Spring 事务管理的功能,开启对声明式事务管理支持。
@Transactional(propagation isolation timeout readOnly rollbackFor noRollbackFor value)用在接口或方法上,当用在接口上时代表该接口中的所有方法均开启事务;当用在方法上代表该方法开启事务;该注解可配置事务属性。注意:该注解一般加在业务层接口中而不会添加到其实现类上,以此来降低耦合。
@Transactional注解属性解释
propagation控制事务的传播行为,即当前事务如何与外部事务交互。传播行为共有7种,分别为:REQUIRED(默认)、REQUIRES_NEWSUPPORTSNOT_SUPPORTEDMANDATORYNEVERNESTED
isolation定义事务的隔离级别,控制不同事务之间如何隔离。共有四种:READ_UNCOMMITTEDREAD_COMMITTEDREPEATABLE_READSERIALIZABLE。MySQL默认为REPEATABLE_READ;Oracle默认为READ_COMMITTED
timeout事务的超时时间(单位:秒),如果事务在指定时间内没有完成,Spring 会自动回滚事务。默认没有超时限制。
readOnly事务是否为只读事务,若是则返回true,默认为false。建议查询时设置为true
rollbackFor指定哪些异常应该触发事务回滚。可以指定多个异常类,多个异常类之间使用逗号分隔。比如:rollbackFor = {Exception1.class, Exception2.class}
noRollbackForrollback-for 相对,指定哪些异常不会触发事务回滚
  • 注意

    • 使用Spring核心配置文件和配置类结合的方式时:若不在Spring的核心配置类中加上@EnableTransactionManagement注解则需要在Spring的核心配置文件中加上如下代码来启用 Spring 事务管理的功能,开启对声明式事务管理支持。

      `<tx:annotation-driven transaction-manager="transactionManager"/>`
      
    • @Transactional注解在接口或类上使用时,该接口或类中的所有方法均开启事务;若该注解不仅在接口或类上使用,还在该接口或类中的方法上使用时,则事务属性的配置以使用在方法上的@Transactional注解为准

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

IT机器猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值