Spring知识点总结-DX的笔记

什么是Spring?

优点

  • 轻量级的开源框架
    • 轻量级:非侵入性。基于spring框架开发的类可以不依赖于spring的API
    • 作用:管理java对象,控制对象的生命周期(IOC)
    • MVC三层架构
      • 控制层 controller
      • 业务层 service
      • 持久层 dao
  • 控制反转IOC、面向切面AOP
  • 支持事务、整合现有的框架技术
  • 一句话概述:Spring是一个轻量级的控制反转(IOC)和面向切面(AOP)的容器(框架)

Spring的组成(七个模块)

请添加图片描述
在这里插入图片描述

  • Spring框架是一个分层架构,由七个模块组成

  • Spring模块构建在核心容器之上

  • 每个模块(或组件)可以单独存在,也可以和其他模块联合使用

  • 1.核心容器

    • 核心容器定义了创建、配置和管理bean的方式,提供 Spring 框架的基本功能
    • 核心容器的主要组件是 BeanFactory ,BeanFactory 使用控制反转(IOC)模式将应用程序的配置和依赖性规范与实际的应用程序代码分开
    • 核心容器包括
      • spring-core:模块提供了框架的基本组成部分,包括 IoC 和依赖注入功能
      • spring-beans:提供 BeanFactory
      • spring-context:Context 模块继承自 Bean 模块,并且添加了国际化(比如,使用资源束)、事件传播、资源加载和透明地创建上下文(比如,通过 Servelet 容器)等功能
      • spring-context-support:提供了强大的表达式语言,用于在运行时查询和操作对象图
      • spring-expression(SpEL,Spring 表达式语言,Spring Expression Language)
      • 等模块组成
  • 2.Spring上下文

    • Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息
    • 包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能
  • 3.Spring AOP

    • 通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中
    • 所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象
    • Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务
    • 通过使用 Spring AOP,不用依赖组件,就可以 将声明性事务管理集成到应用程序中
  • 4.Spring DAO

    • JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息
    • 异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)
    • Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构
  • 5.Spring ORM

    • Spring框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具
    • 其中包括 JDO、Hibernate 和 iBatis SQL Map,所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构
  • 6.Spring Web模块

    • Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文
    • 所以,Spring 框架支持与 Jakarta Struts 的集成
    • Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作
  • 7.Spring MVC 框架

    • MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现
    • 通过策略接口,MVC 框架变成为高度可配置的
    • MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI

IOC(控制反转)

  • 控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法
  • 控制反转:将开发者手中的对Java对象的控制权交给Spring框架的过程。通过配置Bean实现
  • IOC容器:存放Spring创建出来对象的一个容器
  • IoC是Spring框架的核心内容,可以使用XML配置,也可以使用注解,新版本的Spring也可以零配置实现IoC
  • 控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式
  • 在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)

bean标签

属性介绍
  1. id
    • 是 bean的唯一标识
    • 整个IOC 容器 id 值不允许重复,使用名称作为key
  2. name
    • 一个bean的名称,可以存在多个,多个之间使用逗号分隔
    • 如果没有定义name属性,默认id都会当做name。
  3. class
    • bean的具体的类型,包名和类名组成。
  4. scope
    • bean的作用域
  5. prototype
    • 非单例,每次获取都会创建一个新的bean对象。
  6. singleton
    • 单例,多次获取永远同一个bean, 默认值。
  7. request
    • 一次请求,基于web项目的bean的作用域。
  8. session
    • 一次会话,基于web项目的bean的作用域。
  9. lazy-init
    • 延迟初始化
    • 默认加载配置文件时,bean对象就会被初始化,
    • azy-init则是获取时才会初始化
    • 只针对单例模式有效,非单例每次获取都会创建,没有延迟初始化的意义
  10. depends-on
    • 初始化时依赖的对象
    • 当前对象初始化前需先初始化depends-on指定的对象
  11. init-method
    • 对象初始化后,调用的方法
  12. destroy-method
    • 对象销毁时,调用的方法
  13. autowire
    • 属性自动装配
  14. byName
    • 根据属性名称装配
  15. byType
    • 根据类型装配
  16. autowire-candidate
    • 是否允许作为自动装配的候选项
    • true 作为自动装配的候选项
    • false 不作为自动装配的候选项
  17. primary
    • 优先使用该bean
    • 因为Spring需要支持使用类型查找对象,在一个大类型下,可能存在多个小类型
    • 如果根据大类型装配属性时,不知道使用哪个具体的对象,则可以根据primary
使用流程
  • maven依赖配置

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.18</version>
    </dependency>
    
  • applicationContext.xml

    <!--一个bean标签就是一个对象-->
    <bean id="hello1" class="com.dx.bean.HelloSpring"/>
    <!--当有多个同类型的bean时,使用类型获取时会报错,需要设置primary属性-->
    <bean id="hello" class="com.dx.bean.HelloSpring" primary="true"/>
    
  • test.java

    /**
     * 获取对象,并调用其方法
     * */
    @Test
    public void iocTest(){
        //1.初始化ioc容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        //2.从容器中取出对象
        //方式1:通过name获取Bean对象,如果有多个此类型的bean对象时,获取标识的主bean,如果没有标识则抛异常
        HelloSpring hello = (HelloSpring) ac.getBean("hello");
        //方式2:通过bean的类型获取Bean对象
        HelloSpring hello1 = ac.getBean(HelloSpring.class);
        //方式3:通过name和类型获取Bean对象
        HelloSpring hello2 = ac.getBean("hello",HelloSpring.class);
        hello.show();
    }
    
bean的作用域
  • singleton: 单例,默认

    • 一个bean标签在整个IOC容器中仅会创建一个对象
    • 在IOC容器创建完成之后,立即创建,无论是否使用到此bean
  • prototype: 原型(多例)

    • 一个bean标签在整个IOC容器中会创建多个对象
    • 在IOC容器初始化时不会创建任何对象,只有在使用bean时,才会去创建对象,每次创建的都是新对象
  • 用法

    <bean id="book" class="com.bjpowernode.bean.Book" scope="singleton"/>
    <bean id="book" class="com.bjpowernode.bean.Book" scope="prototype"/>
    
bean的生命周期
  • 通过构造方法创建Bean对象

  • 为Bean对象中的成员变量赋值

  • 调用Bean对象中初始化方法(init-method)

  • Bean对象的使用

  • 当容器关闭之前,调用Bean对象的销毁方法(destroy-method)

  • applicationContext.xml

    <!--需要设定初始化方法和销毁方法,否则不执行-->
    <bean id="dog" class="com.bjpowernode.bean.Dog" init-method="init" destroy-method="destroy">
    	<property name="name" value="哈士奇"/>
    </bean>
    
  • Dog.java

    public class Dog {
        private String name;
        public Dog(){
            System.out.println("Dog对象被创建...");
        }
        public void init(){
            System.out.println("Dog对象的初始化方法...");
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            System.out.println("name属性被赋值...");
            this.name = name;
        }
        public void destroy(){
            System.out.println("Dog对象的销毁方法...");
        }
    }
    
  • test.java

    @Test
    public void lifeCycle() {
        System.out.println("=============IOC容器初始化阶段===============");
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
    
        System.out.println("=============对象使用阶段===============");
        Dog dog = ac.getBean("dog", Dog.class);
        System.out.println(dog);
        System.out.println(dog.getName());
        
        System.out.println("=============IOC容器关闭阶段===============");
        ac.close();
    }
    
depends-on属性
  • 先创建指定的bean,再创建自己

  • <!--depends-on 会创建好指定的bean后再创建该bean-->
    <bean id="emp" class="com.dx.bean.Emp" depends-on="dept"/>
    <bean id="dept" class="com.dx.bean.Dept"/>
    

创建bean对象的四种方式

  1. 构造方法

    • 通过反射机制调用类中无参或有参构造方法来是实现Bean的创建

    • <!--使用构造方法创建 (Spring默认的创建方式)-->
      <bean id="cat" class="com.dx.bean2.Cat"/>
      
    • public class Cat {
          public String name;
          @Override
          public String toString() {
              return name + "猫咪对象";
          }
      }
      
    • 测试类(下同)

      @Test
      public void factory() {
          ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
          Cat cat = ac.getBean("cat", Cat.class);
          System.out.println(cat);
      }
      
  2. 静态工厂,实例工厂

    • 通过一个工厂类来间接实现对象创建,将Bean对象放置在IOC容器

    • <!--静态工厂  factory-method指定获取对象的方法名-->
      <bean id="cat1" class="com.dx.factory.CatStaticFactory" factory-method="getInstance"/>
      
      /* Cat对象的静态工厂 */
      public class CatStaticFactory {
          public static Cat getInstance(){
              Cat cat = new Cat();
              cat.name = "加菲猫";
              return cat;
          }
      }
      
    • <!--实例工厂-->
      <bean id="factory" class="com.dx.factory.CatInstanceFactory"/>
      <bean id="cat2" factory-method="getInstance" factory-bean="factory"/>
      
      /* Cat对象的实例工厂 */
      public class CatInstanceFactory {
      
          public Cat getInstance(){
              Cat cat = new Cat();
              cat.name = "黑猫警长";
              return cat;
          }
      }
      
  3. FactoryBean接口

    • <!--FactoryBean-->
      <bean id="cat3" class="com.dx.factory.CatFactoryBean"/>
      
      /* 实现FactoryBean接口 */
      public class CatFactoryBean implements FactoryBean<Cat> {
          /*获取对象*/
          @Override
          public Cat getObject() throws Exception {
              Cat cat = new Cat();
              cat.name = "小花猫";
              return cat;
          }
          /*获取对象的Class类型*/
          @Override
          public Class<?> getObjectType() {
              return Cat.class;
          }
          /*配置当前对象是否为单例模式*/
          @Override
          public boolean isSingleton() {
              return true;
          }
      }
      
  4. 注解

    略,在后面

依赖注入(DI)

  • Dependency Injection

  • 依赖:在A类中使用B类:A类对B类产生依赖关系。

    • public class Classes{}
      
      public class Student{
         	String sname;
          Integer age;
          Double score;
          Classes classes;
          public static void main(String[] args){
              Student s1 = new Student();
          }
      }
      
    • 可以说Student类对String, Integer, Double, Classes类产生了依赖关系

  • 注入:为依赖类型的变量赋值

  • 所谓控制反转就是:获得依赖对象的方式反转了

注入属性的类型
  1. 简单类型数据: 使用字符串可以表示的数据 int, double, boolean, Date
  2. 自定义对象(POJO)
  3. 集合,数组
属性注入的三种方式
  1. 构造方法

    public class Car {
        private Integer id;
        private String name;
        private String type;
        private Double price;
        
        public Car(Integer id, String name, String type, Double price) {
            this.id = id;
            this.name = name;
            this.type = type;
            this.price = price;
        }
    }
    
    <!--构造方法注入-->
    <bean id="car" class="com.dx.bean2.Car">
        <!--默认按照参数顺序注入,乱序会报错-->
        <constructor-arg value="001"/>
        <constructor-arg value="奥迪"/>
        <constructor-arg value="轿车"/>
        <constructor-arg value="49.0"/>
        <!--
            可以使用属性来指定构造方法的参数
                value 构造方法参数值
                index 构造方法参数索引
                name 构造方法参数名称(推荐)
                type 构造方法参数类型
            -->
        <!--使用index-->
        <constructor-arg value="奥迪" index="1"/>
        <constructor-arg value="001" index="0"/>
        <constructor-arg value="49.0" index="3"/>
        <constructor-arg value="轿车" index="2"/>
        <!--使用name-->
        <constructor-arg value="奥迪" name="name"/>
        <constructor-arg value="001" name="id"/>
        <constructor-arg value="49.0" name="price"/>
        <constructor-arg value="轿车" name="type"/>
    </bean>
    
  2. set方法

    • public class Student {
          private String name;
          private Integer age;
          private Double score;
          private Boolean gender;
          private Date birthday;
          private Address address;
          private String[] courseNames;
          private List<String> list;
          private Set<Integer> set;
          private Map<String, Double> map;
          private Properties properties;
          //set和get方法这里省略了
          //......
      }
      
      public class Address {
          private String city;
          private String area;
          private String street;
          //set...... get......
      }
      
      <!--set方法注入-->
      <bean id="student" class="com.dx.bean2.Student">
          <!--name中配置的不是成员变量名称,而是set方法的名称(去除set之后首字母小写后的名称)-->
          <property name="name" value="小明"/>
          <property name="age" value="22"/>
          <property name="score" value="86.5"/>
          <property name="gender" value="true"/>
          <!--spring中默认支持的日期格式为yyyy/MM/dd-->
          <property name="birthday" value="1998/05/19"/>
      
          <!--自定义类型的数据依赖注入: ref属性配置IOC容器中的beanName-->
          <!--方式1 -->
          <!--        <property name="address" ref="address"/>-->
          <!--方式2 -->
          <!--        <property name="address">-->
          <!--            <ref bean="address"/>-->
          <!--        </property>-->
          <!--方式3:内部bean-->
          <property name="address">
              <bean class="com.dx.bean2.Address">
                  <property name="city" value="北京市"/>
                  <property name="area" value="海淀区"/>
                  <property name="street" value="人民路"/>
              </bean>
          </property>
      
          <!--数组-->
          <property name="courseNames">
              <array>
                  <value>语文</value>
                  <value>数学</value>
                  <value>外语</value>
              </array>
          </property>
      
          <!--list集合-->
          <property name="list">
              <list>
                  <value>aaa</value>
                  <value>bbb</value>
                  <value>ccc</value>
                  <value>ddd</value>
              </list>
          </property>
      
          <!--set集合-->
          <property name="set">
              <set>
                  <value>10</value>
                  <value>20</value>
                  <value>30</value>
                  <value>40</value>
              </set>
          </property>
      
          <!--map集合-->
          <property name="map">
              <map>
                  <entry key="one" value="1.2"/>
                  <entry key="two" value="2.6"/>
                  <entry key="three" value="3.14"/>
              </map>
          </property>
      
          <!--properties-->
          <property name="properties">
              <props>
                  <prop key="jdbc.driver">com.mysql.jdbc.Driver</prop>
                  <prop key="jdbc.url">jdbc:mysql://localhost:3306/test</prop>
                  <prop key="jdbc.username">root</prop>
                  <prop key="jdbc.password">123</prop>
              </props>
          </property>
      </bean>
      
      <bean id="address" class="com.dx.bean2.Address">
          <property name="city" value="郑州市"/>
          <property name="area" value="金水区"/>
          <property name="street" value="民航路"/>
      </bean>
      
  3. 注解

    略,在后面

p/c命名空间注入(扩展)
  • P命名空间注入(注入属性: properties)

    导入约束 : xmlns:p="http://www.springframework.org/schema/p"
    <!--P命名空间 , 属性依然要设置set方法-->
    <bean id="user" class="com.dx.pojo.User" p:name="dx" p:age="18"/>
    
  • C命名空间注入(注入构造方法的参数: Constructor)

    导入约束 : xmlns:c="http://www.springframework.org/schema/c"
    <!--C命名空间 , 属性依然要设置set方法-->
    <bean id="user" class="com.dx.pojo.User" c:name="dx" c:age="18"/>
    

自动装配

  • 在进行自定类型的数据依赖注入时,IOC容器会自动查找匹配对象,实施依赖注入

  • 条件:仅限于自定义类型的数据(POJO)

  • 实现:autowire属性

  • 类型:

    • no: 不自动装配

    • byName: 根据属性名称匹配,如果没有找到Bean,不进行自动装配,不会报错。

    • byType: 根据属性类型匹配,如果没有找到此类型Bean,不进行自动装配,不会报错。

      但是如果找到多个没有标识Primary的Bean,就是报错。

    • default: 默认值,会根据父标签beans标签中default-autowire属性来取值。

  • 缺点: 不灵活,不能实现即能根据名称匹配又能根据类型匹配

  • 实现

    public class Order {
        private String orderNum;
        private Double price;
        private Customer customer;
        //get set....
    }
    public class Customer {
        private String username;
        private String password;
        //get set....
    }
    
    <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"
            default-autowire="byName">
    
            <!--autowire自动装配-->
            <bean id="order" class="com.dx.bean2.Order" autowire="byType">
                <property name="orderNum" value="XA20220712001"/>
                <property name="price" value="3998"/>
            </bean>
    
            <bean id="cust" class="com.dx.bean2.Customer" primary="true">
                <property name="username" value="tom"/>
                <property name="password" value="123"/>
            </bean>
            <bean id="customer" class="com.dx.bean2.Customer">
                <property name="username" value="jerry"/>
                <property name="password" value="321"/>
            </bean>
    </beans>
    
    @Test
    public void autowire() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        Order order = ac.getBean("order", Order.class);
        System.out.println(order);
    }
    

使用注解

  1. 组件扫描
  • 先配置命名空间和标签规范。配置在当前项目中哪些包中注解是生效的。

  • xmlns:context="http://www.springframework.org/schema/context"
    
    http://www.springframework.org/schema/context
    http://www.springframework.org/schema/context/spring-context.xsd
    
  1. 配置Bean

    • @Component
    • @Controller
    • @Service
    • @Repository
    • 此以上四种注解在IOC模块中功能上没有区别,都是配置Bean。
    • 不建议大家混用。在Spring项目中,控制层使用@Controller,业务层@Service,持久层@Repository,@Component配置除三层架构之外的类。
    • @Scope:配置作用域,例如:@Scope(“prototype”)配置为多例模式
  2. 依赖注入

    • @Value 简单类型
    • @Autowired 自定义类型
      • Spring中的注解。先根据类型匹配,再根据名称匹配
      • @Autowired(required=false):默认为true,如果没有找到bean会报错,设为false后,不会报错
      • @Qualifier(“beanName”):当有多个bean时,根据名字指定bean
      • @Primary:当有多个bean切无法自动匹配时,可使用这个注解设置主bean
    • @Resource 自定义类型
      • jdk中的注解,spring作了扩展支持。先根据名称匹配,再根据类型匹配
  3. 使用方法

    • // @Controller
      // @Service
      // @Repository
      @Component("dd")
      // @Primary
      public class Department {
          @Value("10")
          private Integer id;
          @Value("研发部")
          private String dname;
          //get... set...
      }
      @Data
      @NoArgsConstructor
      @AllArgsConstructor
      @Component("abc")
      public class Employee {
          @Value("123")
          private Integer id;
          @Value("张三")
          private String ename;
          @Autowired
          @Qualifier("dd")    //通过注解指定bean
      //    @Resource
          private Department department;
      }
      
    •   <!--使用注解-->
        <!--0.在beans标签中加上命名空间和标签规范
                       xmlns:context="http://www.springframework.org/schema/context"
                       http://www.springframework.org/schema/context
                       http://www.springframework.org/schema/context/spring-context.xsd
               -->
        <!--1.扫描基础包,此包下所有类中的注解都会生效,以及此包所有子包类的注解会生效-->
        <context:component-scan base-package="com.dx"/>
      
        <!--配置除注解外的其他bean-->
        <bean id="department" class="com.dx.bean2.Department">
            <property name="id" value="666"/>
            <property name="dname" value="李四"/>
        </bean>
      
    •    @Test
         public void annotation() {
             ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
             //依赖注入简单类型
             Department department = ac.getBean("dd", Department.class);
             System.out.println(department);
             //依赖注入自定义类型
             Employee employee = ac.getBean("abc", Employee.class);
             System.out.println(employee);
         }
      

代理模式(了解)

静态代理

  • 实现方法

    public interface UserDao {
        void insert();
        void update();
        void delete(int id);
        List<String> select();
    }
    
    public class UserDaoImpl implements UserDao {
        @Override
        public void insert() {
            System.out.println("UserDaoImpl---->insert...........");
        }
        @Override
        public void update() {
            System.out.println("UserDaoImpl---->update...........");
        }
        @Override
        public void delete(int id) {
            System.out.println("UserDaoImpl---->delete,id="+id);
        }
        @Override
        public List<String> select() {
            System.out.println("UserDaoImpl---->select...........");
            return Arrays.asList("111","222","333");
        }
    }
    
    /**
    * 静态代理
    * */
    public class UserStaticProxy implements UserDao {
        private UserDao userDao = null;
        public UserStaticProxy(UserDao userDao) {
            this.userDao = userDao;
        }
        @Override
        public void insert() {
            System.out.println("代理 增加的功能--->insert");
            userDao.insert();
        }
        @Override
        public void update() {
            System.out.println("代理 增加的功能--->update");
            userDao.update();
        }
        @Override
        public void delete(int id) {
            System.out.println("代理 增加的功能--->delete   id="+id);
            userDao.delete(id);
        }
        @Override
        public List<String> select() {
            System.out.println("代理 增加的功能--->select");
            return userDao.select();
        }
    }
    
    /**
    * 静态代理
    * */
    @Test
    public void StaticProxy(){
        UserStaticProxy userDaoProxy = new UserStaticProxy(new UserDaoImpl());
        userDaoProxy.insert();
        userDaoProxy.update();
        userDaoProxy.delete(10);
        List<String> list = userDaoProxy.select();
        System.out.println(list);
    }
    

动态代理

  • 动态代理的代理类是动态生成的,静态代理的代理类是我们提前写好的

  • 动态代理分为两类

    • 一类是基于接口动态代理----JDK动态代理
    • 一类是基于类的动态代理-----cglib
  • 一个动态代理 , 一般代理某一类业务,可以代理多个类,代理的是接口!

  • JdkProxy 实现方式

    public class JdkProxy implements InvocationHandler {
        /**
         * 执行被代理对象中的方法
         * @param proxy 被代理对象
         * @param method 目标方法
         * @param args 目标方法的参数值
         * @return 目标方法的返回值
         */
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是代理类添加的辅助业务。");
            //执行目标业务方法,利用反射
            Object result = method.invoke(targetClass.newInstance(), args);
            return result;
        }
        
        private Class targetClass;
        /**
         * 获取代理对象方法
         * 代理类对象:new JdbProxy(), 作用:生产代理对象
         * 代理对象:创建代理对象Proxy.newProxyInstance(),JDK代理对象实现了目标类接口的一个种对象
         */
        public Object getProxy(Class targetClass) {
            this.targetClass =targetClass;
            /*
             * newProxyInstance(loader,interfaces,this);生成代理对象
             * 三个参数分别如下
             * ClassLoader loader   :业务类的类加载器
             * Class<?>[] interfaces:业务类实现的所有接口
             * InvocationHandler h  :代理类对象(此类)
             */
            //业务类的类加载器
            ClassLoader classLoader = targetClass.getClassLoader();
            //业务类实现的所有接口
            Class[] interfaces = this.targetClass.getInterfaces();
            //生成代理对象
            Object proxyObject = Proxy.newProxyInstance(classLoader, interfaces, this);
            return proxyObject;
        }
    }
    
  • CglibProxy 实现方式

    /**
     * cglib动态代理
     * 对目标没有要求,普通类就可以,不用实现接口
     * 代理类是目标类的子类(父子继承)
     * */
    public class CglibProxy implements MethodInterceptor {
        /**
         * 执行目标方法
         * @param o 代理对象
         * @param method 目标方法对象
         * @param objects 目标方法参数值
         * @param methodProxy 代理方法的代理对象
         * @return 目标方法的返回值
         */
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("我是代理类添加的辅助业务。");
            //执行目标业务方法
            Object result = method.invoke(targetClass.newInstance(), objects);
            return result;
        }
        
        private Class targetClass;
        /**
         * 获取代理对象方法
         */
        public Object getProxy(Class targetClass) {
            this.targetClass =targetClass;
            Enhancer enhancer = new Enhancer();
            //目标对象设置为代理对象的父类
            enhancer.setSuperclass(targetClass);
            //设置回调,调用intercept方法
            enhancer.setCallback(this);
            return enhancer.create();
        }
    }
    
  • 测试类

    /**
      * 动态代理
      * 两种方式,效果一样
      * */
    @Test
    public void JdkProxy(){
        //使用JdkProxy
    //	UserDao userDaoProxy = (UserDao) new JdkProxy().getProxy(UserDaoImpl.class);
        //使用CglibProxy
        UserDao userDaoProxy = (UserDao) new CglibProxy().getProxy(UserDaoImpl.class);
        userDaoProxy.insert();
        userDaoProxy.update();
        userDaoProxy.delete(100);
        System.out.println(userDaoProxy.select());
    }
    

AOP(面向切面)

概念

  • 面向切面编程,是一种新的方法论,是对OOP面向对象编程的补充
  • 横切关注点:跨越应用程序多个模块的方法或功能。与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …
  • 切面(Aspect):横切关注点,跨越多个模块的零散功能,组成的独立的、应用于其他模块之中的模块,是一个类
  • 通知(Advice):切面需要完成的工作,是类中的一个方法
  • 连接点(JoinPoint):某个程序的某个执行位置。例如:save()方法执行之前, update()方法执行之后
  • 切点(PointCut):多个模块的连接点组成为切点。例如:所有service类中的所有方法执行之前
  • 目标(Target):被通知对象
  • 代理(Proxy):向目标对象应用通知之后创建的对象
  • AOP的核心思想:在不改变原来的代码的情况下,实现了对原有功能的增强

通知类型

  • 通知会根据连接点不同,分成以下四种通知,第五种是前四种的合成:
    1. 前置通知:方法执行之前
    2. 后置通知:方法执行之后。无法获取返回的数据
    3. 返回通知:方法返回数据之后,表示方法正常结束。可以获取返回的数据
    4. 异常通知:方法抛出异常之后,表示方法非正常结束。可以获取抛出的异常
    5. 环绕通知:以上四种通知的综合性通知

简单实现AOP

  1. 引入依赖

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.21</version>
    </dependency>
    
  2. 切面类

    /**
     * 切面类,用来记录日志
     * 1.无需实现任何接口
     * 2.无需使用反射机制调用目标方法
     * 3.无侵入
     * */
    public class LogAspect {
        //JoinPoint对象封装了Aop中切面方法的信息
        public void beforeMethod(JoinPoint joinPoint){
            System.out.println("我是前置通知日志...");
        }
        public void afterMethod(JoinPoint joinPoint){
            System.out.println("我是后置通知日志...");
        }
        public void returnMethod(JoinPoint joinPoint,Object result){
            System.out.println("我是返回通知日志...返回值:"+result);
        }
        public void throwMethod(JoinPoint joinPoint,Exception e){
            System.out.println("我是异常通知日志...异常为:"+e.getMessage());
        }
        //环绕通知
        public void aroundMethod(ProceedingJoinPoint proceedingJoinPoint) {
            System.out.println("环绕通知的前置配置");
            try {
                Object proceed = proceedingJoinPoint.proceed();
                System.out.println("环绕通知的返回结果="+proceed);
            } catch (Throwable e) {
                System.out.println("环绕通知的异常=" + e.getMessage());
            }
            System.out.println("环绕通知的后置配置");
        }
    }
    
  3. 写applicationContext.xml

    • <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:aop="http://www.springframework.org/schema/aop"
             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
             http://www.springframework.org/schema/aop
             http://www.springframework.org/schema/aop/spring-aop.xsd">
          
      <!--	0.添加新的命名空间和标签规范
      	xmlns:aop="http://www.springframework.org/schema/aop"
      	http://www.springframework.org/schema/aop
      	http://www.springframework.org/schema/aop/spring-aop.xsd	-->
          <!--1.配置bean-->
          <bean id="user" class="com.dx.dao.impl.UserDaoImpl"/>
          <bean id="role" class="com.dx.dao.impl.RoleDaoImpl"/>
          <bean id="log" class="com.dx.aspect.LogAspect"/>
          <!--2.配置切面,需要添加新的命名空间和标签规范-->
          <aop:config>
              <!--写一个公共的切面表达式,可以直接调用-->
              <aop:pointcut id="exp" expression="execution(* com.dx.dao.impl.*.*(..))"/>
              <!--指定bean对象为切面对象-->
              <aop:aspect ref="log">
                  <!--配置通知-->
                  <aop:before method="beforeMethod" pointcut="execution(* com.dx.dao.impl.*Impl.*(..))"/>
                  <aop:after method="afterMethod" pointcut-ref="exp"/>
                  <aop:after-returning method="returnMethod" returning="result"
                                       pointcut="execution(* com.dx.dao.impl.UserDaoImpl.select(..))"/>
                  <aop:after-throwing method="throwMethod" throwing="e"
                                       pointcut="execution(* com.dx.dao.impl.UserDaoImpl.delete(..))"/>
                  <aop:around method="aroundMethod" pointcut-ref="exp"/>
              </aop:aspect>
          </aop:config>
      </beans>
      
  • 说明
    • 切点表达式

             编写规则:execution()包裹
             1.单个方法
             语法:execution(方法访问修饰符 方法返回值类型 方法所在包名.方法所在类名.方法名(参数类型))
             例如:execution(public void com.bjpowernode.dao.impl.UserDaoImpl.insert())
             2.多个方法,通配符*
             语法:execution(* *.*.*(..))
             第一星不限制方法访问修饰符和返回值类型
             第二星不限制方法所在的包
             第三星不限制方法所在的类
             第四星不限制方法名称
             两个点不限制方法的参数
             例如:execution(* com.bjpowernode.dao.impl.UserDaoImpl.*(..))
             例如:execution(* com.bjpowernode.dao.impl.*Impl.*(..))
             例如:execution(* com.bjpowernode.*.service.impl.*Impl.*(..))
      
    • 配置通知

      aop:before前置通知
      aop:after后置通知
      aop:after-returning返回通知
      aop:after-throwing异常通知
      属性:
          method: 切面对象的方法名称
          pointcut: 切点表达式, 将通知应用在那些方法上的一种表达式
          pointcut-ref: 切面表达式的引用
          returning: 是返回通知中的独有属性,配置接收方法的返回值参数
          throwing: 是异常通知中的独有属性,配置接收方法抛出异常对象
      

使用注解实现

  • 切面类

    /**
     * 切面类
     * 使用注解配置
     * */
    @Aspect
    @Order(3)  //设置切面执行顺序
    public class OtherAspect {
        //配置一个公共的切面表达式,供其他注解调用
        @Pointcut("execution(* com.dx.dao.impl.*.*(..))")
        public void exp(){}
        
        @Before("execution(* com.dx.dao.impl.*.*(..))")
        public void before(){
            System.out.println("Other的------前置通知...");
        }
        @After("exp()")
        public void after(){
            System.out.println("Other的------后置通知日志...");
        }
    //    @AfterReturning
    //    @AfterThrowing
    }
    
  • applicationContext.xml

    <!--配置bean-->
    <bean id="other" class="com.dx.aspect.OtherAspect"/>
    
    <!--开启AOP注解-->
    <aop:aspectj-autoproxy/>
    

多切面

  • 通知采用先进后出的原则
  • 前置通知先执行的切面,后置通知后执行
  • 可通过order属性配置切面的执行顺序,也可以使用@Order(1)注解
  • 属性值为整数类型,数字越小越优先
  • 在这里插入图片描述
  • <aop:aspect ref="other" order="2">
        <aop:before method="before" pointcut-ref="exp"/>
        <aop:after method="after" pointcut-ref="exp"/>
    </aop:aspect>
    

Spring与Mybatis整合

  1. 导入依赖

    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.18</version>
        </dependency>
        <!--aop切面-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.21</version>
        </dependency>
        <!--mybaits提供的与spring整合的依赖包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <!--jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.4</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.1</version>
        </dependency>
        <!--分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.24</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
    </dependencies>
    
  2. 配置文件

    • db.properties
      #mysql
      jdbc.mysql.driverClassName=com.mysql.cj.jdbc.Driver
      jdbc.mysql.url=jdbc:mysql://localhost:3306/student?serverTimezone=UTC
      jdbc.mysql.username=root
      jdbc.mysql.password=111111
      
    • log4j.properties
      # 全局日志配置
      log4j.rootLogger=DEBUG, stdout
      # MyBatis 日志配置
      #局部日志记录配置
      log4j.logger.org.mybatis.example.BlogMapper=TRACE
      # 控制台输出
      log4j.appender.stdout=org.apache.log4j.ConsoleAppender
      log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
      log4j.appender.stdout.layout.ConversionPattern=%d %p %c.%M() --%m%n
      
      ### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
      log4j.appender.file = org.apache.log4j.DailyRollingFileAppender
      log4j.appender.file.File = D:/logs/test.log
      #log4j.appender.file.Append = true
      #log4j.appender.file.Threshold = DEBUG
      log4j.appender.file.layout = org.apache.log4j.PatternLayout
      log4j.appender.file.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
      
      ### 输出DEBUG 级别以上的日志到=E://logs/error.log ###
      log4j.appender.E = org.apache.log4j.DailyRollingFileAppender
      log4j.appender.E.File =E://logs/error.log 
      log4j.appender.E.Append = true
      log4j.appender.E.Threshold = ERROR 
      log4j.appender.E.layout = org.apache.log4j.PatternLayout
      log4j.appender.E.layout.ConversionPattern = %-d{yyyy-MM-dd HH:mm:ss}  [ %t:%r ] - [ %p ]  %m%n
      
  3. entity

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Emp {
        private Integer empno;
        private String ename;
        private String job;
        private Integer mgr;
        private Date hiredate;
        private Double sal;
        private Double comm;
        private Integer deptno;
    }
    
  4. dao

    public interface EmpDao {
        List<Emp> select();
        Emp selectById(Integer empno);
        int insert(Emp entity);
        int update(Emp entity);
        int deleteById(Integer empno);
    }
    
  5. EmpMapper.xml

    <mapper namespace="com.dx.dao.EmpDao">
        <select id="select" resultType="emp">
            select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp order by empno asc
        </select>
        <select id="selectById" parameterType="int" resultType="Emp">
            select empno,ename,job,mgr,hiredate,sal,comm,deptno from emp where empno=#{empno}
        </select>
        <insert id="insert" parameterType="com.dx.entity.Emp">
            insert into emp(ename,job,mgr,hiredate,sal,comm,deptno)
            values(#{ename},#{job},#{mgr},#{hiredate},#{sal},#{comm},#{deptno})
        </insert>
        <update id="update" parameterType="com.dx.entity.Emp">
            update emp set ename=#{ename},job=#{job},mgr=#{mgr},hiredate=#{hiredate},sal=#{sal},comm=#{comm},deptno=#{deptno}
            where empno=#{empno}
        </update>
        <delete id="deleteById" parameterType="int">
            delete from emp where empno=#{empno}
        </delete>
    </mapper>
    
  6. mybatis-config.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
      PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <setting name="logImpl" value="org.apache.ibatis.logging.stdout.StdOutImpl"/>
        </settings>
        <typeAliases>
            <package name="com.dx.entity"/>
        </typeAliases>
        <plugins>
            <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
        </plugins>
    </configuration>
    
  7. applicationContext.xml⭐

    <?xml version="1.0" encoding="UTF-8"?>
    <beans
    	xmlns="http://www.springframework.org/schema/beans"
    	xmlns:context="http://www.springframework.org/schema/context"
    	xmlns:aop="http://www.springframework.org/schema/aop"
    	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
    	http://www.springframework.org/schema/context
    	http://www.springframework.org/schema/context/spring-context.xsd
    	http://www.springframework.org/schema/aop
    	http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    	<!--组件扫描,使包下的Spring注解生效-->
    	<context:component-scan base-package="com.dx"/>
    
    	<!--加载第三方properties文件-->
    	<context:property-placeholder location="classpath:db.properties"/>
    
    	<!--数据源
    	spring-jdbc模块中提供一个内置数据源,用于测试	-->
    	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    		<property name="driverClassName" value="${jdbc.mysql.driverClassName}"/>
    		<property name="url" value="${jdbc.mysql.url}"/>
    		<property name="username" value="${jdbc.mysql.username}"/>
    		<property name="password" value="${jdbc.mysql.password}"/>
    	</bean>
    
    	<!--1.将SqlSessionFactory对象交给springIOC管理-->
    	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    		<!--注入数据源-->
    		<property name="dataSource" ref="dataSource"/>
    		<!--加载mybatis核心配置文件-->
    		<property name="configLocation" value="classpath:mybatis-config.xml"/>
    		<!--加载mapper文件: 通过通配符*加载多个mapper文件-->
    		<property name="mapperLocations" value="classpath:mapper/*.xml"/>
    	</bean>
    
    	<!--2.mapper代理对象交给springIOC管理-->
    	<!--a.单独配置mapper代理对象-->
    	<bean id="empDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
    		<!--注入SqlSessionFactory-->
    		<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    		<!--指定mapper接口-->
    		<property name="mapperInterface" value="com.dx.dao.EmpDao"/>
    	</bean>
    	<!--b.批量配置mapper代理对象-->
    	<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    		<!--注入SqlSessionFactory-->
    		<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
    		<!--扫描基础包: 如果有多个持久层包,以逗号分隔即可-->
    		<property name="basePackage" value="com.dx.dao"/>
    	</bean>
    </beans>
    
  8. EmpController.java

    /**
     * 替代Serlvet
     * Controller web层:接收请求,处理请求,给予响应
     */
    @Controller
    public class EmpController {
        @Autowired
        private EmpService empService;
        public PageInfo<Emp> page(Integer pageNumber, Integer pageSize){
            return empService.page(pageNumber, pageSize);
        }
        public List<Emp> list(){
            return empService.list();
        }
        public Emp get(Integer empno){
            return empService.getById(empno);
        }
        public boolean save(Emp entity){
            return empService.save(entity);
        }
        public boolean edit(Emp entity){
            return empService.edit(entity);
        }
        public boolean remove(Integer empno){
            return empService.remove(empno);
        }
    }
    
  9. 测试类

    @Test
    public void pageTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmpController empController = ac.getBean(EmpController.class);
        PageInfo<Emp> pageInfo = empController.page(2, 5);
        List<Emp> list = pageInfo.getList();
        for (Emp emp : list) {
            System.out.println(emp);
        }
        System.out.println("总页数:" + pageInfo.getPages());
        System.out.println("总记录数:" + pageInfo.getTotal());
    }
    @Test
    public void listTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmpController empController = ac.getBean(EmpController.class);
        List<Emp> list = empController.list();
        for (Emp emp : list) {
            System.out.println(emp);
        }
    }
    @Test
    public void insertTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmpController empController = ac.getBean(EmpController.class);
        Emp emp = new Emp(null,"小杨","经理",100,new Date(),6500.0,1000.0,10);
        boolean result = empController.save(emp);
        System.out.println(result);
    }
    @Test
    public void updateTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmpController empController = ac.getBean(EmpController.class);
        Emp emp = new Emp(7935,"小陈","经理",100,new Date(),6500.0,1000.0,10);
        boolean result = empController.edit(emp);
        System.out.println(result);
    }
    @Test
    public void deleteTest(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        EmpController empController = ac.getBean(EmpController.class);
        boolean result = empController.remove(7937);
        System.out.println(result);
    }
    

事务

  • 事务是一系列动作(执行SQL),一个事务是一个独立的不可拆分工作单元,事务中执行的所有SQL同时成功或同时失败
  • 事务的四个特性
    • 原子性 A
    • 一致性 C
    • 隔离性 I
    • 持久性 D

编程式事务

//设置关闭自动提交
connection.setAutoCommit(false);
try{
    //执行多个SQL语句
    ...
    connection.commit();
}catch(Exception e){
    connection.rollback();
}

声明式事务

  • 利用AOP来管理事务,由spring来实现事务的提交或回滚。

  • 声明式事务:1.配置事务管理器(切面)2.配置事务切入点

  • JDBC的事务管理器:DataSourceTransactionManager,它类似于一个切面

  • Hibernate的事务管理类器:HibernateTransactionManager

事务属性

  1. 事务的传播性(propagation)
  • 当多个事务“相遇”(事务方法调用事务方法)时,spring该如何去处理。
  • REQUIRED: 如果一个事务方法调用另一个事务方法,那么两个方法中的事务会合二为一
  • REQUIRES_NEW:如果一个事务方法调用另一个事务方法,被调用事务方法会在自己独立的事务中运行
  • 在这里插入图片描述
  1. 事务隔离级别(isolation)
  • 数据库中自带的特性,在SQL标准中有四种:读未提交、读已提交、可重复读、可序列化。

  • 读未提交:读取其他事务没有提交的数据,读取其他事务的内存。会引起脏读、不可重复读、幻读。

  • 读已提交:仅能读取到其他事务提交的数据。可以避免脏读,但是会发生不可重复读、幻读。

  • 可重复读:在一个事务操作表时,将它操作的列锁定,使其他事务无法进行列的修改。可以避免脏读、不可重复读,但是会发生幻读。

  • 可序列化:在一个事务操作表时,将它操作的整个表锁定,使其他事务无法进行表的修改。可以避免所有的并发问题,但是效率低。

  • 事务并发出现的三种问题

    • 脏读:TX1读取了TX2的未提交的数据,此时TX2回滚了,那么TX1读取到的数据就是无效数据。
    • 不可重复读:TX读取了一个表中的字段值,再次读取时发现数据变化了。
    • 幻读:TX读取了表中记录数,再次读取时发现数据量变化了。
    • mysql数据库拥有以上四种事务隔离级别,默认的隔离级别是可重复读。
    • oracle数据库拥有读已提交和可序列化两种,默认的事务隔离级别为读已提交。
    • 注意:DEFAULT值表示spring的事务隔离级别配置,按照数据库默认隔离级别。
  1. 回滚列表,不回滚列表
  2. 只读事务
  3. 超时控制
    • 默认执行时间没有限制
    • 单位为秒
    • 如果在指定时间内事务没有执行完成,就是抛出异常,事务回滚

事务的配置

声明式事务
  • @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class Account {
        private Integer id;
        private String name;
        private Double balance;
    }
    
  • public interface AccountDao {
        void updateBalance(Account account);
        Account selectById(Integer id);
    }
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <mapper namespace="com.dx.dao.AccountDao">
        <select id="selectById" parameterType="int" resultType="Account">
            select id, name, balance from account where id=#{id}
        </select>
        <update id="updateBalance" parameterType="com.dx.entity.Account">
            update account set balance=#{balance} where id=#{id}
        </update>
    </mapper>
    
  • public interface AccountService {
        /*转账*/
        void transferMoney(Integer fromId, Integer toId, Double money) throws Exception;
    }
    
    @Service
    // @Transactional(rollbackFor = Exception.class)
    public class AccountServiceImpl implements AccountService {
        @Autowired
        private AccountDao accountDao;
        @Override
        public void transferMoney(Integer fromId, Integer toId, Double money) throws Exception {
            Account fromAccount = accountDao.selectById(fromId);
            Account toAccount = accountDao.selectById(toId);
            fromAccount.setBalance(fromAccount.getBalance()-money);
            accountDao.updateBalance(fromAccount);
            System.out.println("扣款成功");
            //模拟异常:Spring默认回滚的是运行时异常,可以在配置文件或注解中配置
    //        System.out.println(10/0);
            Class.forName("aaa");
            //事务超时,自动回滚
    //        Thread.sleep(5000);
            toAccount.setBalance(toAccount.getBalance()+money);
            accountDao.updateBalance(toAccount);
            System.out.println("转账完成");
        }
    }
    
  • <!--******************* Spring事务 *******************-->
    <!--注解式事务:开启事务-->
    <tx:annotation-driven/>
    <!--声明式事务-->
    <!--1.事务管理器,spring提供-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <!--2.事务拦截器和其属性-->
    <tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
        <tx:attributes>
            <!--方法名、传播行为、隔离级别、回滚异常范围、超时时间(秒)-->
            <tx:method name="transferMoney" propagation="REQUIRES_NEW" isolation="DEFAULT" rollback-for="java.lang.Exception" timeout="4"/>
            <!--方法名可以使用通配符,查询语句可以设为只读,不参与事务-->
            <tx:method name="query*" rollback-for="java.lang.Exception" read-only="true"/>
            <tx:method name="query*" rollback-for="java.lang.Exception" read-only="true"/>
            <tx:method name="find*" rollback-for="java.lang.Exception" read-only="true"/>
            <tx:method name="get*" rollback-for="java.lang.Exception" read-only="true"/>
            <tx:method name="list*" rollback-for="java.lang.Exception" read-only="true"/>
            <tx:method name="page*" rollback-for="java.lang.Exception" read-only="true"/>
            <tx:method name="add*" rollback-for="java.lang.Exception"/>
            <tx:method name="save*" rollback-for="java.lang.Exception"/>
            <tx:method name="insert*" rollback-for="java.lang.Exception"/>
            <tx:method name="edit*" rollback-for="java.lang.Exception"/>
            <tx:method name="update*" rollback-for="java.lang.Exception"/>
            <tx:method name="delete*" rollback-for="java.lang.Exception"/>
            <tx:method name="del*" rollback-for="java.lang.Exception"/>
            <tx:method name="remove*" rollback-for="java.lang.Exception"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>
    <!--3.事务切入点-->
    <aop:config>
        <aop:pointcut id="exp" expression="execution(* com.dx.service.impl.*Impl.*(..))"/>
        <aop:advisor advice-ref="transactionInterceptor" pointcut-ref="exp"/>
    </aop:config>
    
注解式事务
  • public interface AccountService {
        /*多商品一起付款*/
        void payOrder(Integer fromId, Map<Integer,Double> map) throws Exception;
    }
    
    @Service
    @Transactional(rollbackFor = Exception.class)
    public class AccountServiceImpl implements AccountService {
        //注入自己
        @Autowired
        private AccountService accountService;
        @Override
        public void transferMoney(Integer fromId, Integer toId, Double money) throws Exception {
            Account fromAccount = accountDao.selectById(fromId);
            Account toAccount = accountDao.selectById(toId);
            fromAccount.setBalance(fromAccount.getBalance()-money);
            accountDao.updateBalance(fromAccount);
            System.out.println("扣款成功");
            toAccount.setBalance(toAccount.getBalance()+money);
            accountDao.updateBalance(toAccount);
            System.out.println("转账完成");
        }
        @Override
        //注解式事务
        @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,
                rollbackFor = Exception.class, rollbackForClassName = "java.lang.Exception",
                readOnly = true, timeout = 5)
        public void payOrder(Integer fromId, Map<Integer, Double> map) throws Exception {
            Set<Integer> toIdSet = map.keySet();
            System.out.println(toIdSet.size());
            for (Integer toId : toIdSet) {
                System.out.println("toId="+toId);
                double money = map.get(toId);
                accountService.transferMoney(fromId,toId,money);
                //模拟异常
                String s=null;
                s.trim();
            }
            System.out.println("订单支付成功");
        }
    }
    
  • <!--注解式事务:开启事务-->
    <tx:annotation-driven/>
    

mId, Map<Integer,Double> map) throws Exception;
}


```java
@Service
@Transactional(rollbackFor = Exception.class)
public class AccountServiceImpl implements AccountService {
    //注入自己
    @Autowired
    private AccountService accountService;
    @Override
    public void transferMoney(Integer fromId, Integer toId, Double money) throws Exception {
        Account fromAccount = accountDao.selectById(fromId);
        Account toAccount = accountDao.selectById(toId);
        fromAccount.setBalance(fromAccount.getBalance()-money);
        accountDao.updateBalance(fromAccount);
        System.out.println("扣款成功");
        toAccount.setBalance(toAccount.getBalance()+money);
        accountDao.updateBalance(toAccount);
        System.out.println("转账完成");
    }
    @Override
    //注解式事务
    @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED,
            rollbackFor = Exception.class, rollbackForClassName = "java.lang.Exception",
            readOnly = true, timeout = 5)
    public void payOrder(Integer fromId, Map<Integer, Double> map) throws Exception {
        Set<Integer> toIdSet = map.keySet();
        System.out.println(toIdSet.size());
        for (Integer toId : toIdSet) {
            System.out.println("toId="+toId);
            double money = map.get(toId);
            accountService.transferMoney(fromId,toId,money);
            //模拟异常
            String s=null;
            s.trim();
        }
        System.out.println("订单支付成功");
    }
}
  • <!--注解式事务:开启事务-->
    <tx:annotation-driven/>
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值