Spring的基础使用

一、控制反转-Inversion Of Control

基本概念

       内聚: 内聚标志一个模块内各个元素彼此结合的紧密程度,它是信息隐蔽和局部化概念的自然扩展。
       耦合: 耦合是软件结构中各模块之间相互连接的一种度量,耦合强弱取决于模块间接口的复杂程度、进入或访问一个模块的点以及通过接口的数据。
       划分模块的一个准则就是高内聚低耦合。
2、Spring 相关
      full-stack级别的轻量级开源框架,Spring的IOC容器是个Map结构
       控制反转(Inversion of Control),简称IOC,把创建对象的权利交给框架,它包括依赖注入(Dependency Injection)和依赖查找(Dependency Lookup),目的就是为了削减计算机程序间的耦合(解除代码中的依赖关系)。

1、Spring 工厂类结构在这里插入图片描述

(1)BeanFactory 和 ApplicationContext 的区别
BeanFactory 才是 Spring 容器中的顶层接口。
ApplicationContext 是它的子接口。
BeanFactory 和 ApplicationContext 的区别:
       创建对象的时间点不一样。
              ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
              BeanFactory:什么使用什么时候创建对象。

(2)ApplicationContext 接口的实现类
ClassPathXmlApplicationContext:
       它是从类的根路径下加载配置文件 推荐使用这种
FileSystemXmlApplicationContext:
       它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
AnnotationConfigApplicationContext:
       当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。

(3)要用到的包
在这里插入图片描述
       maven配置如下,后面的依赖包会自动加载进来

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

在这里插入图片描述

2、bean标签的属性

作用:
       用于配置对象让 spring 来创建的。
       默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。
属性:
       id:给对象在容器中提供一个唯一标识。用于获取对象。
       class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
       scope:指定对象的作用范围。
              * singleton : 默认值,单例的.
              * prototype : 多例的.
              * request : WEB 项目中,Spring 创建一个 Bean 的对象, 将对象存入到 request 域中.
              * session : WEB 项目中,Spring 创建一个 Bean 的对象, 将对象存入到 session 域中.
              * global session : WEB 项目中,应用在 Portlet 环境。如果没有 Portlet 环境那么globalSession 相当于 session.
       init-method:指定类中的初始化方法名称。
       destroy-method:指定类中销毁方法名称。

3、bean 的作用范围和生命周期

       单例对象:scope=“singleton”
              一个应用只有一个对象的实例。它的作用范围就是整个引用。
              生命周期:
                     对象出生:当应用加载,创建容器时,对象就被创建了。
                     对象活着:只要容器在,对象一直活着。
                     对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
       多例对象:scope=“prototype”
       每次访问对象时,都会重新创建对象实例。
       生命周期:
              对象出生:当使用对象时,创建新的对象实例。
              对象活着:只要对象在使用中,就一直活着。
              对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。

4、实例化 Bean 的三种方式

这个是需要实例化的类

public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        System.out.println("执行了保存用户方法");
    }
}

第一种:使用默认的无参构造函数

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 在默认情况下:
         它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。-->
    <bean id="demo1" class="com.xcm.service.impl.UserServiceImpl"></bean>
</beans>

第二种方式:spring 管理静态工厂-使用静态工厂的方法创建对象

/**
 * 模拟一个静态工厂,创建业务层实现类
 */
public class StaticFactory {
    public static UserService createUserService(){
        return new UserServiceImpl();
    }
}
    <!-- 此种方式是:
        使用 StaticFactory 类中的静态方法 createAccountService 创建对象,并存入 spring 容器
        id 属性:指定 bean 的 id,用于从容器中获取
        class 属性:指定静态工厂的全限定类名
        factory-method 属性:指定生产对象的静态方法
    -->
    <bean id="demo2" class="com.xcm.factory.StaticFactory" factory-method="createUserService"/>

第三种方式:spring 管理实例工厂- 使用实例工厂的方法创建对象

/**
 * 模拟一个实例工厂,创建业务层实现类
 * 此工厂创建对象,必须现有工厂实例对象,再调用方法
 */
public class InstanceFactory {
    public UserService createAccountService(){
        return new UserServiceImpl();
    }
}
 <!-- 此种方式是:
    先把工厂的创建交给 spring 来管理。
    然后在使用工厂的 bean 来调用里面的方法
    factory-bean 属性:用于指定实例工厂 bean 的 id。
    factory-method 属性:用于指定实例工厂中创建对象的方法。
-->
<bean id="instanceFactory" class="com.xcm.factory.InstanceFactory"></bean>
<bean id="demo3" factory-bean="instanceFactory" factory-method="createAccountService"></bean>
5、spring 的依赖注入

(1)依赖注入的概念
       依赖注入:Dependency Injection。它是 spring 框架核心 ioc 的具体实现。
       我们的程序在编写时,通过控制反转,把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。ioc 解耦只是降低他们的依赖关系,但不会消除。例如:我们的业务层仍会调用持久层的方法。那这种业务层和持久层的依赖关系,在使用 spring 之后,就让 spring 来维护了。
       简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取。

(2) 构造函数注入

public class UserServiceImpl2 implements UserService {

    private String name;
    private Integer age;
    private Date birthday;

    public UserServiceImpl2(String name, Integer age, Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    @Override
    public void save() {
        System.out.println(name + " " + age + " " + birthday);
    }
}
    <!-- 使用构造函数的方式,给 service 中的属性传值
        要求:
            类中需要提供一个对应参数列表的构造函数。
        涉及的标签:
            constructor-arg
                属性:
                    index:指定参数在构造函数参数列表的索引位置
                    type:指定参数在构造函数中的数据类型
                    name:指定参数在构造函数中的名称 用这个找给谁赋值
                    =======上面三个都是找给谁赋值,下面两个指的是赋什么值的==============
                    value:它能赋的值是基本数据类型和 String 类型
                    ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
    -->
    <bean id="demo4" class="com.xcm.service.impl.UserServiceImpl2">
        <constructor-arg name="name" value="鱼化龙"></constructor-arg>
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>

    <bean id="now" class="java.util.Date"></bean>

(3)set 方法注入

public class UserServiceImpl3 implements UserService {

    private String name;
    private Integer age;
    private Date birthday;

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    @Override
    public void save() {
        System.out.println(name + " " + age + " " + birthday);
    }
}
    <!-- 通过配置文件给 bean 中的属性传值:使用 set 方法的方式
        涉及的标签:
            property
            属性:
                name:找的是类中 set 方法后面的部分
                ref:给属性赋值是其他 bean 类型的
                value:给属性赋值是基本数据类型和 string 类型的
        实际开发中,此种方式用的较多。
    -->
    <bean id="demo5" class="com.xcm.service.impl.UserServiceImpl3">
        <property name="name" value="鱼化龙"></property>
        <property name="age" value="18"></property>
        <property name="birthday" ref="now"></property>
    </bean>

    <bean id="now" class="java.util.Date"></bean>

(4)使用 p 名称空间注入数据(本质还是调用 set 方法)

<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd">

	<!-- xmlns:p="http://www.springframework.org/schema/p" -->
    <!-- 此种方式是通过在 xml 中导入 p 名称空间,使用 p:propertyName 来注入数据,它的本质仍然是调用类中的
        set 方法实现注入功能。-->
    <bean id="demo6" class="com.xcm.service.impl.UserServiceImpl3"
        p:name="熊出没" p:age="18" p:birthday-ref="now"/>

    <bean id="now" class="java.util.Date"></bean>

</beans>

(5)注入集合属性

import com.xcm.service.UserService;
import lombok.Setter;

import java.util.*;

@Setter
public class UserServiceImpl4 implements UserService {

    private String[] myStrs;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;


    @Override
    public void save() {
        System.out.println(Arrays.toString(myStrs));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }
}

       集合内的数据只需要结构相同,标签可以互换

<?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">

    <!-- 注入集合数据
        List 结构的:
            array,list,set
        Map 结构的
            map,entry,props,prop
    -->
    <bean id="demo7" class="com.xcm.service.impl.UserServiceImpl4">
        <!-- 在注入集合数据时,只要结构相同,标签可以互换 -->
        <!-- 给数组注入数据 -->
        <property name="myStrs">
            <set>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </set>
        </property>
        <!-- 注入 list 集合数据 -->
        <property name="myList">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
        <!-- 注入 set 集合数据 -->
        <property name="mySet">
            <list>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </list>
        </property>
        <!-- 注入 Map 数据 -->
        <property name="myMap">
            <props>
                <prop key="testA">aaa</prop>
                <prop key="testB">bbb</prop>
            </props>
        </property>
        <!-- 注入 properties 数据 -->
        <property name="myProps">
            <map>
                <entry key="testA" value="aaa"></entry>
                <entry key="testB">
                    <value>bbb</value>
                </entry>
            </map>
        </property>
    </bean>

    <bean id="now" class="java.util.Date"></bean>

</beans>

二、基于注解的 IOC

注意: 在基于注解的配置中,我们还要多拷贝一个 aop 的 jar 包。

1、创建 spring 的 xml 配置文件并开启对注解的支持

基于注解整合时,导入约束时需要多导入一个 context 名称空间下的约束。

JdbcTemplate
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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">

    <!-- 告知 spring 创建容器时要扫描的包 -->
    <context:component-scan base-package="com.xcm"></context:component-scan>
</beans>
2、Spring 常用注解

标签分类

(1)用于创建对象的

      和xml配置文件中bean标签实现的功能一样,相当于:
                  <bean id="" class="">

      Ⅰ:@Component
            作用:
                  用于把当前类对象存入Spring容器中。相当于在 xml 中配置一个 bean。
            属性:
                  value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写。

      Ⅱ: @Controller、 @Service、 @Repository
            他们三个注解都是@Component的衍生注解,他们的作用及属性都是一模一样的。
            他们只不过是提供了更加明确的语义化。
                  @Controller:一般用于表现层的注解。
                  @Service:一般用于业务层的注解。
                  @Repository:一般用于持久层的注解。
            细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。

(2)用于注入数据的

      和bean标签中的property标签的作用是一样的,相当于:
                  <property name="" ref="">
                  <property name="" value="">

      Ⅰ:@Autowired
            作用: 默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null 值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用。
                        默认状态下,如果IOC容器没有任何bean的类型和要注入的变量类型 匹配,则报错。
                        默认状态下,如果IOC容器中有多个类型匹配时,需要id加以区别,否 则也报错
            出现位置: 可以是在变量上,也可以是在方法上
            细节: 在使用注解注入时,set方法不是必须的

      Ⅱ: @Qualifier
            作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和@Autowire 一起使用;但是给方法参数注入时,可以独立使用。
            属性: value:指定 bean 的 id。

      Ⅲ: @Resource(这个注解属于J2EE的)
            作用: @Resource(这个注解属于J2EE的),默认安照名称进行装配,名称可以通过name属性进行指定, 如果没有指定name属性,当注解写在字段上时,默认取字段名进行按照名称查找,如果注解写在setter方法上默认取属性名进行装配。 当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
Java代码
            属性: name:指定 bean 的 id。

以上三个注入都只能注入其他bean类型的数据,而基本类型和String无法使用上述注解实现,另外集合类型的注入只能通过XML来实现

      Ⅳ:@Value
            作用: 注入基本数据类型和 String 类型数据的
            属性: value:用于指定值。他可以使用Spring中的 SpEL(也就是Spring的EL表达式)
                        SpEL的写法:$(表达式)

(3)用于改变作用范围的

      和bean标签中的scope属性作用是一样的,相当于:
                  <bean id="" class="" scope="">
      Ⅰ:@Scope
            作用: 指定 bean 的作用范围。
            属性: value:指定范围的值。
                   取值:singleton prototype request session globalsession

(4)和生命周期相关的

      和bean标签中使用init-method和destroy-method的作用是一样的,相当于:
                  <bean id="" class="" init-method="" destroy-method="" />

      Ⅰ:@PostConstruct
            作用: 用于指定初始化方法。
      Ⅱ:@PreDestroy
            作用: 用于指定初始化方法。

(5)关于 Spring 注解和 XML 的选择问题

      注解的优势:配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。
      XML 的优势:修改时,不用改源码。不涉及重新编译和部署。

XML 配置注解配置
Bean定义<bean id="" class="…"/>@Component,
衍生类@Repository、@Service、@Controller
Bean名称通过 id 或者 name 指定@Component(“person”)
Bean注入<property>或者通过 p 命名空间@Autowired 按类型注入
@Qualifier 按名称注入
Bean生命过程、作用范围init-method、destroy-method
范围 scope 属性
@PostConstruct 初始化
@PreDestroy 销毁
@Score 设置作用范围哦
适合场景Bean来自第三方,使用其他Bean的实现类由自己开发

      基于注解的 spring IoC 配置中,bean 对象的特点和基于 XML 配置是一模一样的。

3、Spring 的新注解

      @Configuration
            作用:指定当前类是一个配置类
            细节:当配置类作为AnnotationConfigApplicationContext对象创 建的参数时,该注解可以不写。

      @ComponentScan
            作用:用于通过注解指定spring在创建容器时要扫描的包
            属性:value和basePackages的作用是一样的,都是用于指定创 建容器时要扫描的包。
                        我们使用此注解就等同于在xml中配置了:
                              <context:component-scan base-package=“com.xcm”/>

      @Bean
            作用:用于把当前方法的返回值作为bean对象存入spring的ioc 容器中
            属性:name用于指定bean的id。当不写时,默认值是当前方法 的名称
            细节:当我们使用注解配置方法时,如果方法有参数,spring框 架会去容器中查找有没有可用的bean对象。
                        查找的方式和Autowired注解的作用是一样的

      @ Import
             作用:用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。
             属性: value用于指定其他配置类的字节码。
                        当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类

      @PropertySource
            作用:用于指定properties文件的位置
            属性: value指定文件的名称和路径。
                        关键字:classpath,表示类路径下

       @PreDestroy
            作用: 用于指定销毁方法。

4、 Spring 整合 Junit

第一步:Maven 配置

 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

        <!-- 数据库 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.6</version>
        </dependency>

		<!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>

		<!-- 测试 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
        </dependency>
    </dependencies>

第二步:使用@RunWith 注解替换原有运行器

第三步:使用@ContextConfiguration 指定 spring 配置文件的位置

5、完整的基于注解 IOC

(1)配置类
主配置类

@ComponentScan(basePackages = {"com.xcm.dao"})
@Import({JdbcConfig.class})
public class SpringConfiguration {

}

数据源配置

@Configuration
@PropertySource("classpath:db.properties")
public class JdbcConfig {

    @Value("${driver}")
    private String driver;
    @Value("${url}")
    private String url;
    @Value("${user}")
    private String username;
    @Value("${password}")
    private String password;

    /**
     * 创建一个数据源,并存入Spring容器中
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean("runner")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }
}

db.properties 配置文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&userSSL=false&serverTimezone=GMT%2B8
user=root
password=

(2)DAO 编写

@Repository
public class UserDao {

    @Autowired
    private QueryRunner runner;

    public void findAll() throws SQLException {
        List<User> users = runner.query("select * from user", new BeanListHandler<>(User.class));
        System.out.println(users);
    }

}

(3)测试类编写

import com.xcm.config.SpringConfiguration;
import com.xcm.dao.UserDao;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.sql.SQLException;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringTest {

    @Autowired
    ApplicationContext ac;

    @Test
    public void test1() throws SQLException {
        //ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        UserDao userDao = ac.getBean("userDao", UserDao.class);
        userDao.findAll();
    }

}

三、面向切面编程 - Aspect Oriented Programming

基本概念

      简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

作用:
      在程序运行期间,不修改源码对已有方法进行增强。
优势:
      减少重复代码
      提高开发效率
      维护方便

AOP 的实现方式:
       使用动态代理技术

动态代理的特点:
      字节码随用随创建,随用随加载。
      它与静态代理的区别也在于此。因为静态代理是字节码一上来就创建好,并完成加载。
      装饰者模式就是静态代理的一种体现。

动态代理常用的有两种方式:
(1)基于接口的动态代理
      提供者:JDK 官方的 Proxy 类。
      要求:被代理类最少实现一个接口。
(2)基于子类的动态代理
      提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
      要求:被代理类不能用 final 修饰的类(最终类)。
参考我的另一篇博客:点击这里

1、Spring 中 AOP 的细节

(1)AOP 相关术语
Joinpoint(连接点):
      所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的
连接点。
Pointcut(切入点):
      所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
Advice(通知/增强):
      所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
      通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
Introduction(引介):
      引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
Target(目标对象):
      代理的目标对象。
Weaving(织入):
      是指把增强应用到目标对象来创建新的代理对象的过程。
      spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
Proxy(代理):
      一个类被 AOP 织入增强后,就产生一个结果代理类。
Aspect(切面):
      是切入点和通知(引介)的结合。

(2)关于代理的选择
      在 spring 中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式。

2、基于 XML 的 AOP 配置

(1)准备工作:
在这里插入图片描述
maven 配置

<dependency>
    <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.2.5.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>aspectj</groupId>
     <artifactId>aspectjweaver</artifactId>
     <version>1.5.4</version>
 </dependency>

(2)配置步骤:

第一步:把通知类用 bean 标签配置起来

<!-- 配置通知 -->
<bean id="txManager" class="com.xcm.utils.TransactionManager">
	<property name="dbAssit" ref="dbAssit"></property>
</bean> 

第二步:使用 aop:config 声明 aop 配置

aop:config:
	作用:用于声明开始 aop 的配置
	
<aop:config>
    <!-- 配置的代码都写在此处 -->
</aop:config>

第三步:使用 aop:aspect 配置切面

aop:aspect:
	作用:
		用于配置切面。
	属性:
		id:给切面提供一个唯一标识。
		ref:引用配置好的通知类 bean 的 id。
		
<aop:aspect id="txAdvice" ref="txManager">
	<!--配置通知的类型要写在此处-->
</aop:aspect>

第四步:使用 aop:pointcut 配置切入点表达式

aop:pointcut:
	作用:
		用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
	属性:
		expression:用于定义切入点表达式。
		id:用于给切入点表达式提供一个唯一标识
		
<aop:pointcut expression="execution(
	public void com.xcm.service.impl.AccountServiceImpl.transfer(
	java.lang.String, java.lang.String, java.lang.Float)
)" id="pt1"/>

第五步:使用 aop:xxx 配置对应的通知类型

aop:before
	作用:
		用于配置前置通知。指定增强的方法在切入点方法之前执行
	属性:
		method:用于指定通知类中的增强方法名称
		ponitcut-ref:用于指定切入点的表达式的引用
		poinitcut:用于指定切入点表达式
	执行时间点:
		切入点方法执行之前执行
<aop:before method="beginTransaction" pointcut-ref="pt1"/>

aop:after-returning
	作用:
		用于配置后置通知
	属性:
		method:指定通知中方法的名称。
		pointct:定义切入点表达式
		pointcut-ref:指定切入点表达式的引用
	执行时间点:
		切入点方法正常执行之后。它和异常通知只能有一个执行
<aop:after-returning method="commit" pointcut-ref="pt1"/>


aop:after-throwing
	作用:
		用于配置异常通知
	属性:
		method:指定通知中方法的名称。
		pointct:定义切入点表达式
		pointcut-ref:指定切入点表达式的引用
	执行时间点:
		切入点方法执行产生异常后执行。它和后置通知只能执行一个
	<aop:after-throwing method="rollback" pointcut-ref="pt1"/>


aop:after
	作用:
		用于配置最终通知
	属性:
		method:指定通知中方法的名称。
		pointct:定义切入点表达式
		pointcut-ref:指定切入点表达式的引用
	执行时间点:
		无论切入点方法执行时是否有异常,它都会在其后面执行。
<aop:after method="release" pointcut-ref="pt1"/>
3、切入点表达式说明
execution:匹配方法的执行(常用)
	execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
	全匹配方式:
	public void
com.xcm.service.impl.AccountServiceImpl.saveAccount(com.xcm.domain.Account)
	访问修饰符可以省略
		void
com.xcm.service.impl.AccountServiceImpl.saveAccount(com.xcm.domain.Account)
	返回值可以使用*号,表示任意返回值
		*
com.xcm.service.impl.AccountServiceImpl.saveAccount(com.xcm.domain.Account)
	包名可以使用*号,表示任意包,但是有几级包,需要写几个*
		* *.*.*.*.AccountServiceImpl.saveAccount(com.xcm.domain.Account)
	使用..来表示当前包,及其子包
		* com..AccountServiceImpl.saveAccount(com.xcm.domain.Account)
	类名可以使用*号,表示任意类
		* com..*.saveAccount(com.xcm.domain.Account)
	方法名可以使用*号,表示任意方法
		* com..*.*( com.xcm.domain.Account)
	参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
		* com..*.*(*)
	参数列表可以使用..表示有无参数均可,有参数可以是任意类型
		* com..*.*(..)
	全通配方式:
		* *..*.*(..)

注:
	通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。
	execution(* com.xcm.service.impl.*.*(..))
4、环绕通知
配置方式:
<aop:config>
	<aop:pointcut expression="execution(* com.xcm.service.impl.*.*(..))" id="pt1"/>
	<aop:aspect id="txAdvice" ref="txManager">
		<!-- 配置环绕通知 -->
		<aop:around method="transactionAround" pointcut-ref="pt1"/>
	</aop:aspect>
</aop:config>


aop:around:
	作用:
		用于配置环绕通知
	属性:
		method:指定通知中方法的名称。
		pointct:定义切入点表达式
		pointcut-ref:指定切入点表达式的引用
	说明:
		它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
	注意:
		通常情况下,环绕通知都是独立使用的
5、AOP 测试代码

(1)测试类

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void save() {
        // int i = 1/0;
        System.out.println("执行了用户保存操作");
    }
}

(2)通知类

import org.aspectj.lang.ProceedingJoinPoint;

public class Logger {

    public void beforeLog(){
        System.out.println("前置");
    }

    public void afterReturningLog(){
        System.out.println("后置");
    }

    public void afterThrowingLog(){
        System.out.println("异常");
    }

    public void afterLog(){
        System.out.println("最终");
    }

    public Object aroundLog(ProceedingJoinPoint pjp){
        System.out.println("开始环绕通知");
        Object rtValue = null;

        try {
            Object[] args = pjp.getArgs();

            System.out.println("前置通知");

            rtValue = pjp.proceed(args);

            System.out.println("后置通知");

        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        } finally {
            System.out.println("最终通知");
        }
        return rtValue;
    }

}

(3)bean.xml 配置

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

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

    <!-- 配置通知类 -->
    <bean id="loggerManager" class="com.xcm.utils.Logger"></bean>

    <!-- 声明 aop 配置 -->
    <aop:config>
        <!-- 配置切入点 -->
        <aop:pointcut id="pc1" expression="execution(* com.xcm.service.impl.*.*(..))"></aop:pointcut>
        <aop:pointcut id="pc2" expression="execution(* com.xcm.service.impl.*.find*(..))"></aop:pointcut>
        <aop:aspect id="logAdvice" ref="loggerManager">
            <!-- 前置通知 -->
            <!--<aop:before method="beforeLog" pointcut-ref="pc1"/>-->
            <!-- 后置通知 -->
            <!--<aop:after-returning method="afterReturningLog" pointcut-ref="pc1"/>-->
            <!-- 异常通知 -->
            <!--<aop:after-throwing method="afterThrowingLog" pointcut-ref="pc1"/>-->
            <!-- 最终通知 -->
            <!--<aop:after method="afterLog" pointcut-ref="pc1"/>-->

            <!-- 环绕通知 -->
            <aop:around method="aroundLog" pointcut-ref="pc1"/>
        </aop:aspect>
    </aop:config>
</beans>

(4)测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class BeanTest {

    @Resource
    private UserService userService;

    @Test
    public void test1(){
        userService.save();
    }
}

四、基于注解的 AOP 配置

(1)测试类

@Service
public class UserServiceImpl implements UserService {
    @Override
    public void findAll() {
        System.out.println("执行了查询所有用户操作");
    }

    @Override
    public void save() {
        int i = 1/0;
        System.out.println("执行了用户保存操作");
    }
}

(2)配置文件

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

    <!-- 告知 spring,在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.xcm"/>

    <!-- 开启 spring 对注解 AOP 的支持 -->
    <aop:aspectj-autoproxy/>

</beans>

(2)通知类

@Component("logManager")
@Aspect  // //表明当前类是一个切面类
public class Logger {

    /**
     * @Before
     *  作用:
     *      把当前方法看成是前置通知。
     *  属性:
     *      value:用于指定切入点表达式,还可以指定切入点表达式的引用。
     */
    @Before("execution(* com.xcm.service.impl.*.*(..))")
    public void beforeLog(){
        System.out.println("前置");
    }

    /**
     * @AfterReturning
     *  作用:
     *      把当前方法看成是后置通知。
     *  属性:
     *      value:用于指定切入点表达式,还可以指定切入点表达式的引用
     */
    @AfterReturning("execution(* com.xcm.service.impl.*.*(..))")
    public void afterReturningLog(){
        System.out.println("后置");
    }

    /**
     * @AfterThrowing
     *  作用:
     *      把当前方法看成是异常通知。
     *  属性:
     *      value:用于指定切入点表达式,还可以指定切入点表达式的引用
     */
    @AfterThrowing("execution(* com.xcm.service.impl.*.*(..))")
    public void afterThrowingLog(){
        System.out.println("异常");
    }

    /**
     * @After
     *  作用:
     *      把当前方法看成是最终通知。
     *  属性:
     *      value:用于指定切入点表达式,还可以指定切入点表达式的引用
     */
    @After("execution(* com.xcm.service.impl.*.*(..))")
    public void afterLog(){
        System.out.println("最终");
    }

}

环绕通知

@Component("logManager")
@Aspect  // //表明当前类是一个切面类
public class Logger {
    /**
     * @Pointcut
     *  作用:
     *      指定切入点表达式
     *  属性:
     *      value:指定表达式的内容
     */
    @Pointcut("execution(* com.xcm.service.impl.*.*(..))")
    private void pc1(){}

    /**
     *@Around
     *  作用:
     *      把当前方法看成是环绕通知。
     *  属性:
     *      value:用于指定切入点表达式,还可以指定切入点表达式的引用。
     */
    @Around("pc1()")  //注意:千万别忘了写括号
    public Object aroundLog(ProceedingJoinPoint pjp){
        System.out.println("开始环绕通知");
        Object rtValue = null;

        try {
            Object[] args = pjp.getArgs();

            System.out.println("前置通知");

            rtValue = pjp.proceed(args);

            System.out.println("后置通知");

        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        } finally {
            System.out.println("最终通知");
        }
        return rtValue;
    }

}

(3)如果不写 XML 文件

@Configuration
@ComponentScan(basePackages = {"com.xcm"})
@EnableAspectJAutoProxy
public class SpringConfiguration {
}

五、 Spring 中的 JdbcTemplate

1、JdbcTemplate 概述

      它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装。spring 框架为我们提供了很多
的操作模板类。
      操作关系型数据的:
            JdbcTemplate
            HibernateTemplate
      操作 nosql 数据库的:
            RedisTemplate
      操作消息队列的:
            JmsTemplate
      在导包的时候,除了要导入这个 jar 包
外,还需要导入一个 spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。

2、JdbcTemplate 对象的创建

我们可以参考它的源码:

public JdbcTemplate() {
}
public JdbcTemplate(DataSource dataSource) {
	setDataSource(dataSource);
	afterPropertiesSet();
}
public JdbcTemplate(DataSource dataSource, boolean lazyInit) {
	setDataSource(dataSource);
	setLazyInit(lazyInit);
	afterPropertiesSet();
}

除了默认构造函数之外,都需要提供一个数据源。既然有set方法,依据我们之前学过的依赖注入,我们可以在配置文件中配置这些对象。

文件配置

<?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">

	<!-- 配置一个数据库的操作模板:JdbcTemplate -->
	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"></property>
	</bean>
	
	<!-- 配置数据源 -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
		<property name="url" value="jdbc:mysql:///spring_day02"></property>
		<property name="username" value="root"></property>
		<property name="password" value="1234"></property>
	</bean>
</beans>

基本使用

public class JdbcTemplateDemo2 {
	public static void main(String[] args) {
		//1.获取 Spring 容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据 id 获取 bean 对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		jt.execute("insert into account(name,money)values('eee',500)");
	}
}

更新操作

jt.update("insert into account(name,money)values(?,?)","fff",5000);

查询操作

public class JdbcTemplateDemo3 {
	public static void main(String[] args) {
		//1.获取 Spring 容器
		ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
		//2.根据 id 获取 bean 对象
		JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
		//3.执行操作
		//查询所有
		List<Account> accounts = jt.query("select * from account where money > ? ",
		new AccountRowMapper(), 500);
		for(Account o : accounts){
			System.out.println(o);
		}
	}
}

public class AccountRowMapper implements RowMapper<Account>{
	@Override
	public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
		Account account = new Account();
		account.setId(rs.getInt("id"));
		account.setName(rs.getString("name"));
		account.setMoney(rs.getFloat("money"));
		return account;
	}
}	
3、在 dao 中使用 JdbcTemplate

(1)第一种方式

public class AccountDaoImpl implements IAccountDao {

	private JdbcTemplate jdbcTemplate;
	
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}
	
	@Override
	public void updateAccount(Account account) {
		jdbcTemplate.update("update account set money = ? where id = ?
	",account.getMoney(),account.getId());
	}
}

创建 Dao 时需要注入 jdbcTemplate

(2)第二种方式:让 dao 继承 JdbcDaoSupport
JdbcDaoSupport 是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源:具体源码如下:

public abstract class JdbcDaoSupport extends DaoSupport {

	//定义对象
	private JdbcTemplate jdbcTemplate;
	
	//set 方法注入数据源,判断是否注入了,注入了就创建 JdbcTemplate
	public final void setDataSource(DataSource dataSource) {
		//如果提供了数据源就创建 JdbcTemplate
		if (this.jdbcTemplate == null || dataSource != this.jdbcTemplate.getDataSource(){ 
			this.jdbcTemplate = createJdbcTemplate(dataSource);
			initTemplateConfig();
		}
	}
	
	//使用数据源创建 JdcbTemplate
	protected JdbcTemplate createJdbcTemplate(DataSource dataSource) {
		return new JdbcTemplate(dataSource);
	}
	
	//当然,我们也可以通过注入 JdbcTemplate 对象
	public final void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
		initTemplateConfig();
	}
	
	//使用 getJdbcTmeplate 方法获取操作模板对象
	public final JdbcTemplate getJdbcTemplate() {
		return this.jdbcTemplate;
	}
}

创建自己的实现类

/**
* 账户的持久层实现类
* 此版本 dao,只需要给它的父类注入一个数据源
*/
public class AccountDaoImpl2 extends JdbcDaoSupport implements IAccountDao {
	@Override
	public Account findAccountById(Integer id) {
		//getJdbcTemplate()方法是从父类上继承下来的。
		List<Account> list = getJdbcTemplate().query("select * from account where
		id = ? ",new AccountRowMapper(),id);
		return list.isEmpty()?null:list.get(0);
	}
}

创建 Dao 时需要注入 dataSource

(3)两种方法的区别
第一种在 Dao 类中定义 JdbcTemplate 的方式,适用于所有配置方式(xml 和注解都可以)。
第二种让 Dao 继承 JdbcDaoSupport 的方式,只能用于基于 XML 的方式,注解用不了。

六、 Spring 中的事务控制

第一:JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案。
第二:spring 框架为我们提供了一组事务控制的接口。
第三:spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。

1、Spring 中事务控制的 API 介绍

(1)PlatformTransactionManager

      此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法,如下图:
在这里插入图片描述
我们在开发中都是使用它的实现类,如下图:
真正管理事务的对象
      org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 SpringJDBC 或 iBatis 进行持久化数据时使用
      org.springframework.orm.hibernate5.HibernateTransactionManager 使用 Hibernate 版本进行持久化数据时使用

(2)TransactionDefinition
      它是事务的定义信息对象,里面有如下方法:
在这里插入图片描述

2、基于 XML 配置的事物管理

本案例是模拟银行转账

(1)编写 POJO

@Data
public class Account {
    private Long id;
    private Long uid;
    private Double money;
}

(2)编写 Dao 层

public interface AccountDao {
    // 查询所有用户
    List<Account> findAllAccount();

    // 根据id查询用户
    Account findAccountById(Long id);

    // 更新账户
    int updateAccount(Long id, Double money);
}
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    @Override
    public List<Account> findAllAccount() {
        List<Account> accounts = getJdbcTemplate().query("select * from account", new AccountRowMapper());
        return accounts;
    }

    @Override
    public Account findAccountById(Long id) {
        List<Account> accounts = getJdbcTemplate().query("select * from account where id = ?", new AccountRowMapper(), id);
        if (accounts.size() > 1) {
            throw new RuntimeException("存在两个相同的账户");
        }
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    @Override
    public int updateAccount(Long id, Double money) {
        int i = getJdbcTemplate().update("update account set money = ? where id = ?", money, id);
        return i;
    }
}
public class AccountRowMapper implements RowMapper<Account> {
    @Override
    public Account mapRow(ResultSet rs, int i) throws SQLException {
        Account account = new Account();
        account.setId(rs.getLong("id"));
        account.setUid(rs.getLong("uid"));
        account.setMoney(rs.getDouble("money"));
        return account;
    }
}

(3)编写 Service 层

public interface AccountService {
    // 查询所有账户
    List<Account> findAllAccount();

    // 转账操作
    void transferAccount(Long fromId, Long toId, Double money);
}
public class AccountServiceImpl implements AccountService {

    private AccountDao accountDao;

    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

    @Override
    public void transferAccount(Long fromId, Long toId, Double money) {
        Account from = accountDao.findAccountById(fromId);
        Account to = accountDao.findAccountById(toId);

        accountDao.updateAccount(fromId, from.getMoney() - money);
        // 模拟转账发生异常
        int i = 1/0;
        accountDao.updateAccount(toId, to.getMoney() + money);
    }
}

(4)XML 配置
bean.xml

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

    <!-- 配置 service -->
    <bean id="accountService" class="com.xcm.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <!-- 配置 dao -->
    <bean id="accountDao" class="com.xcm.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 引入数据库配置文件 -->
    <context:property-placeholder ignore-unresolvable="true" location="classpath*:db.properties"/>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <!-- ===============配置事务============== -->

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置事务的通知引用事务管理器 -->
    <tx:advice id="txAdvice">
        <!--配置事务的属性-->
        <tx:attributes>
            <!-- 指定方法名称:是业务核心方法
            read-only:是否是只读事务。默认 false,不只读。
            isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
            propagation:指定事务的传播行为。
            timeout:指定超时时间。默认值为:-1。永不超时。
            rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
            没有默认值,任何异常都回滚。
            no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回
            滚。没有默认值,任何异常都回滚。
            -->
            <tx:method name="*" read-only="false" propagation="REQUIRED"/>
            <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>
    
    <!-- 配置 AOP -->
    <aop:config>
        <!-- 配置 AOP 切入点表达式 -->
        <aop:pointcut id="pt1" expression="execution(* com.xcm.service.impl.*.*(..))"/>
        <!-- 第五步:配置切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"/>
    </aop:config>
</beans>

db.properties

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf8&userSSL=false&serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=

(5)测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class BeanTest {

    @Resource
    AccountService accountService;

    @Test
    public void testFindAllAccount(){
        List<Account> allAccount = accountService.findAllAccount();
        for(Account account : allAccount) {
            System.out.println(account);
        }
    }

    @Test
    public void testTranfer(){
        accountService.transferAccount(3L, 1L, 100.00);
        System.out.println("转账成功");
    }
}
3、基于注解的事物管理

(1)配置步骤

第一步:配置事务管理器并注入数据源

<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
	<property name="dataSource" ref="dataSource"></property>
</bean>

第二步:在业务层使用@Transactional 注解
      该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
      出现接口上,表示该接口的所有实现类都有事务支持。
      出现在类上,表示类中所有方法有事务支持
      出现在方法上,表示方法有事务支持。
      以上三个位置的优先级:方法>类>接口

第三步:在配置文件中开启 spring 对注解事务的支持

<!-- 开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>

(2)使用纯注解的方式
      ① POJO和前面一样

      ② Dao 层编写

@Repository
public class AccountDaoImpl implements AccountDao {

    @Resource
    private JdbcTemplate jdbcTemplate;

    @Override
    public List<Account> findAllAccount() {
        List<Account> accounts = jdbcTemplate.query("select * from account", new AccountRowMapper());
        return accounts;
    }

    @Override
    public Account findAccountById(Long id) {
        List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new AccountRowMapper(), id);
        if (accounts.size() > 1) {
            throw new RuntimeException("存在两个相同的账户");
        }
        return accounts.isEmpty() ? null : accounts.get(0);
    }

    @Override
    public int updateAccount(Long id, Double money) {
        int i = jdbcTemplate.update("update account set money = ? where id = ?", money, id);
        return i;
    }
}

      ③ Service 层编写

@Service
@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {

    @Resource
    private AccountDao accountDao;

    @Override
    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

    @Override
    @Transactional(readOnly = false, propagation = Propagation.REQUIRED)
    public void transferAccount(Long fromId, Long toId, Double money) {
        Account from = accountDao.findAccountById(fromId);
        Account to = accountDao.findAccountById(toId);

        accountDao.updateAccount(fromId, from.getMoney() - money);
        //int i = 1/0;
        accountDao.updateAccount(toId, to.getMoney() + money);
    }
}

      ④ 配置类编写

@Configuration
@ComponentScan({"com.xcm"})
@Import({JdbcConfiguration.class})
@EnableTransactionManagement  // 对事物的支持
public class SpringConfiguration {
}
@Configuration
@PropertySource("classpath:db.properties")
public class JdbcConfiguration {

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

    /**
     * 配置数据源
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 创建
     * @param dataSource
     * @return
     */
    @Bean
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public TransactionManager createTransactionManager(DataSource dataSource){
        DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
        transactionManager.setDataSource(dataSource);
        return transactionManager;
    }
}

      ⑤ 测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class BeanTest2 {

    @Resource
    AccountService accountService;

    @Test
    public void testFindAllAccount(){
        List<Account> allAccount = accountService.findAllAccount();
        for(Account account : allAccount) {
            System.out.println(account);
        }
    }

    @Test
    public void testTranfer(){
        accountService.transferAccount(3L, 1L, 100.00);
        System.out.println("转账成功");
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值