Spring入门(这一篇就够了)

Spring入门

Spring概述

参考博客 https://blog.csdn.net/weixin_42405670/article/details/83048002.
①Spring是一个开源框架

②Spring为简化企业级开发而生,使用Spring开发可以将Bean对象,Dao组件对象,Service组件对象等交给Spring容器来管理,这样使得很多复杂的代码在Spring中开发却变得非常的优雅和简洁,有效的降低代码的耦合度,极大的方便项目的后期维护、升级和扩展。

③Spring是一个IOC(DI)和AOP容器框架。

④Spring的优良特性

[1]非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API

[2]控制反转:IOC——Inversion of Control,指的是将对象的创建权交给Spring去创建。使用Spring之前,对象的创建都是由我们自己在代码中new创建。而使用Spring之后。对象的创建都是由给了Spring框架。

[3]依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用setXX方法去设置,而是通过配置赋值。

[4]面向切面编程:Aspect Oriented Programming——AOP

[5]容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期

[6]组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。

[7]一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)

模块关系
在这里插入图片描述
下面进入正式学习

ApplicationContext三个常用类

ApplicationContext的三个常用类
      ClassPathXmlApplicationContext:可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了
      FileSystemXmlApplicationContext:可以加载磁盘任意路径的配置文件(必须有访问权限)

      AnnotationConfigApplicationContex:它是用于读取注解创建容器的
 第一种比较常见

 容器的两个接口引发的问题:
 ApplicationContext:  单例对象适用      采用此接口
      它在构建核心容器时,创建对象采取的策略是立即加载的方式,也就是说,只要一读取完配置文件马上就创建配置文件中的配置对象
BeanFactory:           多例对象适用
      它在构建核心容器时,创建对象采取的策略是延时加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象

步骤:

        // 1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2.根据id获取Bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        IAccountDao ad = (IAccountDao) ac.getBean("accountDao");

        as.saveAccount();
        // ------BeanFactory-----
//        Resource resource = new ClassPathResource("bean.xml");
//        BeanFactory factory = new XmlBeanFactory(resource);
//        IAccountService as = (IAccountService) factory.getBean("accountService");
//        System.out.println(as);

bean的基本注入方式

<bean id="accountDao" class="cn.fjnu.spring.dao.AccountImpl"></bean>

Spring对bean的管理细节

  1. 创建bean的三种方式
  2. bean对象的作用范围
  3. bean对象的生命周期
<!--创建bean的三种方式-->
       <!--第一种方式:使用默认的构造函数创建。
              在Spring配置文件中使用的bean标签,配以id和class属性后,且没有其他属性和标签时
              采用的就是默认构造函数创建bean对象,此时如果类中没有默认的构造函数,则对象无法创建-->
       <!--
       <bean id="accountService" class="cn.fjnu.spring.service.AccountServiceImpl" />
       -->
       <!--第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)-->
       <!--
       <bean id = "instacneFactory" class="cn.fjnu.spring.factory.InstanceFactory"/>
       <bean id = "accountService" factory-bean="instacneFactory" factory-method="getAccountService"></bean>
       -->
       <!--上面这句的,factory-bean是指定工厂,-method是指定创建该对象的方法-->

       <!--第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
       <bean id = "accountService" class="cn.fjnu.spring.factory.StaticFactory" factory-method="getAccountService"></bean>
       -->
       <!--bean的作用范围调整
              bean标签的scope属性
                  作用:指定bean的作用范围
                  取值:常用的是单例和多例
                     singleton:单例(默认值)
                     prototype: 多例
                     request:作用于web应用的请求范围
                     session:作用于web应用的会话范围
                     global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session

       <bean id="accountService" class="cn.fjnu.spring.service.AccountServiceImpl" scope="prototype"/>
       -->
       <!--bean对象的生命周期
              单例对象
                     出生:当容器创建时对象出生
                     活着:只要容器还在,对象一直活着
                     死亡:容器销毁,对象消亡
                     总结:单例对象的生命周期和容器相同
              多例对象
                     出生: 当我们使用对象时,spring框架为我们创建
                     活着:对象只要在使用就活着
                     死亡:当对象长时间不用,且没有别的对象引用时,由java的垃圾回收器回收
       -->
                         
       <bean id="accountService" class="cn.fjnu.spring.service.AccountServiceImpl"
             scope="prototype" init-method="init" destroy-method="destory"/>

模拟实体工厂

package cn.fjnu.spring.factory;
/*
 *模拟一个工厂类(该类可能是存在于jar包中,我们无法通过修改源码的方式来提供默认的构造函数)
 */

import cn.fjnu.spring.service.AccountServiceImpl;
import cn.fjnu.spring.service.IAccountService;

public class InstanceFactory {
    public IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}

静态工厂

package cn.fjnu.spring.factory;

import cn.fjnu.spring.service.AccountServiceImpl;
import cn.fjnu.spring.service.IAccountService;

public class StaticFactory {
    public static IAccountService getAccountService(){
        return new AccountServiceImpl();
    }
}

生命周期示例

public class AccountServiceImpl implements IAccountService{
    public AccountServiceImpl(){
        System.out.println("对象创建了");
    }
    public void saveAccount() {
        System.out.println("service中的saveAccount方法执行了。。。");
    }
    public void init() {
        System.out.println("对象初始化了。。。");
    }
    public void destory() {
        System.out.println("对象销毁了。。。");
    }
}

Spring中的依赖注入(XML)

       依赖注入:
              Dependency Injection
       IOC:
           降低程序间的耦合(依赖关系)
       依赖关系的管理:
           以后都交给spring来维护
       在当前类需要用到其他类的对象。由spring为我们提供,我们只需在配置文件中说明
       依赖的维护:
          就称之为依赖注入
        依赖注入:
          能注入的类型有三类
              基本类型和String
              其他bean类型(在配置文件中或者在注解配置过的bean)
              复杂类型/集合类型
          注入的方式有三种:
              1.使用构造函数提供
              2.使用set方法提供
              3.使用注解提供

构造函数注入

          使用的标签:constructor-arg
          标签出现的位置:bean标签的内部
          标签中的属性
             type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
             index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。(参数索引的位置从0开始)
             name(常用):用于指定给构造函数中指定名称的参数赋值
             =====================以上三个用于指定给构造函数中哪个参数赋值=================
             value: 用于提供基本类型和String类型的数据
             ref:用于指定其他的bean类型数据。指的就是在spring的IOC核心容器中出现过的对象
          优势:
             在获取对象时,注入数据是必须的操作,否则对象无法创建成功
          弊端:
             改变了bean对象实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。

public class AccountServiceImpl implements IAccountService{
    // 如果是经常变化的数据,不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;
    public AccountServiceImpl(String name,Integer age,Date birthday){
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }
    public void saveAccount() {
        System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
    }

}
<bean id="accountService" class="cn.fjnu.spring.service.AccountServiceImpl">
    <constructor-arg name="name" value="test"></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>

set注入

              涉及的标签:property
              出现位置:bean标签内部
              标签的属性:
              name(常用):用于指定注入时所调用的set方法名称
              value: 用于提供基本类型和String类型的数据
              ref:用于指定其他的bean类型数据。指的就是在spring的IOC核心容器中出现过的对象
           优势:
              创建对象时没有明确的限制,可以直接使用默认的构造函数
           弊端:
              如果有某个成员必须有值,则获取对象时有可能set方法没有执行。
public class AccountServiceImpl2 implements IAccountService{
    // 如果是经常变化的数据,不适用于注入的方式
    private String name;
    private Integer age;

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

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

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

    private Date birthday;

    public void saveAccount() {
        System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
    }

}
<bean id="accountService2" class="cn.fjnu.spring.service.AccountServiceImpl2">
    <property name="name" value="test"></property>
    <property name="age" value="21"></property>
    <property name="birthday" ref="now"></property>
</bean>

复杂类型注入

       用于给list结构集合注入的标签:
       list array set
       用于给Map结构集合注入的标签
       entry props
       结构相同,标签可以互换
public class AccountServiceImpl3 implements IAccountService{

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

    public void setMyArr(String[] myArr) {
        this.myArr = myArr;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }


    public void saveAccount() {
        System.out.println(Arrays.toString(myArr));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }

}
<bean id="accountService3" class="cn.fjnu.spring.service.AccountServiceImpl3">
    <property name="myArr">
    	<array>
    		<value>AAA</value>
    		<value>BBB</value>
   			<value>CCC</value>
    	</array>
    </property>
    <property name="myList">
        <list>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </list>
    </property>
    <property name="mySet">
        <set>
            <value>AAA</value>
            <value>BBB</value>
            <value>CCC</value>
        </set>
    </property>
    <property name="myMap">
        <map>
            <entry key="testA" value="aaa"></entry>
            <entry key="testB">
            <value>bbb</value>
            </entry>
        </map>
    </property>
    <property name="myProps">
        <props>
            <prop key="testC">ccc</prop>
            <prop key="testD">ddd</prop>
        </props>
    </property>
</bean>

注解注入

 曾经xml的配置
 <bean id="accountService" class="cn.fjnu.spring.service.AccountServiceImpl"
       scope="" init-method="" destroy-method=""/>
       <property name="" value="" | ref=""></property>
 
  用于创建对象的
       作用和xml配置文件中编写bean标签<bean>实现的功能是一样的
       @Component
           作用:用于把当前类对象存入Spring容器中
           属性:
               value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写
       @Controller 一般用于表现层
       @Service 一般用于业务层
       @Repository 一般用于持久层
       以上三个注解他们的作用于属性与Component是一样的。
       他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
  用于注入数据的
       他们的作用就和在xml配置文件中的bean便签写一个<property>标签的作用是一样的
       @Autowired:
           作用:自动按照类型注入。只要容器中有唯一的一个Bean对象和要注入的变量类型匹配,就可以注入成功
                如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
                如果ioc容器中有多个类型匹配时:
           出现位置:变量或方法上
           细节:在使用注解注入时,set方法就不是必须的了。
       @Qualifier
           作用:在按照类型注入的基础上,再按照名称注入。
                它在给类成员注入时不能单独使用。但是在给方法参数注入时可以。
           属性:
               value:用于指定注入bean的id
       @Resource
           作用:直接按照bean的id注入。它可以独立使用
           属性:
               name:用于指定bean的id
       以上三个注入只能注入bean类型的数据,而基本类型和String类型无法使用上述注解实现
       另外,集合类型的注入只能通过xml实现
       @Value
           作用:用于注入基本类型和String类型的数据
           属性:
               value:用于指定数据的值。它可以使用spring中的SpEL(也就是spring的el表达式)
                      SpEL的写法:${表达式}
  用于改变作用范围的
       他们的作用就和在bean标签中实现scope属性实现的功能一样
       @Scope
           作用:用于指定bean的作用范围
           属性:
               value:指定范围的值。常用取值:singleton prototype
  和生命周期相关的  (了解)
       他们的作用就和在bean便签中使用init-method和deotroy-method的作用是一样的
       PreDestroy
           作用:用于指定销毁方法
       PostConstruct
           作用:用于指定初始化方法

示例

@Service("accountService")

public class AccountServiceImpl implements IAccountService{
//    @Autowired
//    @Qualifier("accountDao1")
    @Resource(name= "accountDao2")
    private IAccountDao accountDao;
    @PostConstruct
    public void init(){
        System.out.println("对象初始化");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("对象销毁");
    }
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

配置QueryRunner(apache提供的连接数据库的辅助类)

导入依赖

<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.4</version>
</dependency>

配置文件配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置Service-->
    <bean id="accountService" class="com.fjnu.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--配置Dao对象-->
    <bean id="accountDao" class="com.fjnu.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="runner" ref="runner"></property>
    </bean>
    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="user" value="root"></property>
        <property name="password" value="gcm124126"></property>
    </bean>
</beans>

使用示例


/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {

    private QueryRunner runner;

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }
    public List<Account> findAllAccount() {

        try{
            return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

    public Account findAccountById(Integer accountId) {
        try{
            return runner.query("select * from account where id =? ",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void saveAccount(Account account) {
        try{
            runner.update("insert into account(name,money)values (?,?)",account.getName(),account.getMoney());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void updateAccount(Account account) {
        try{
            runner.update("update account set name=?,money=? where id = ?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    public void deleteAccount(Integer accountId) {
        try{
            runner.update("delete from account where id = ?",accountId);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

@Configuration标签(配置类详解)

  该类是一个配置类,作用与bean.xml一样
  spring中的新注解
  Configuration
       作用:指定当前类是一个配置类
  ComponentScan
       作用:用于通过注解指定spring在创建容器时要扫描的包
       属性:
           value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包
                  使用此注解就等同于在xml中配置了:
<context:component-scan base-package="com.fjnu"></context:component-scan>
       细节:
           当配置类作为AnnotationConfigApplicationContext对象创建参数时可以不写
  Bean:
       用于把当前方法的返回值作为bean对象存入spring的ioc容器中
       属性:
           name:用于指定bean的id。当不写时默认值是当前方法的名称
       细节:
           当我们使用注解配置方法时,如果方法有参数,Spring会去容器中查找有没有可用的bean对象
           查找方式和Autowired注解的作用是一样的
  Import:
       用于导入其他的配置类
       属性:
           value:用于指定其他配置类的字节码
                   当我们使用Import的注解之后,有import注解的类就叫父配置类,而导入的都是子配置类
  PropertySource
       用于指定properties文件的位置
       属性:
           value:指定文件名称和路径
                 关键字:classpath,表示类路径下

配置junit进行单元测试

  使用junit进行单元测试,测试配置
  spring 整合Junit配置
       1.导入spring整合junit的jar包(坐标)
       2.使用junit提供的一个注解把原有的main方法替换了,替换成spring提供的
       @RunWith
       3.告知Spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
       @ContextConfiguration
           locations:指定xml文件位置,加上classpath关键字,表示在类路径下
           classes:指定注解类的所在位置
  当我们使用spring5.x版本的时候,要求junit的jar必须是4.12及以上

使用示例(纯注解实现)

SpringConfiguration.java

//@Configuration
@ComponentScan("com.fjnu")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {

}

JdbcConfig.java

/**
 * 和spring连接数据库相关的配置类
 */
//@Configuration

public class JdbcConfig {

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

    /**
     * 用于创建一个QuerRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name = "datasource")
    public DataSource createDataSource(){
        try{
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }
}

配置文件与注解并存(常用方式)

数据库信息通常使用配置文件配置

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!--告知spring容器在创建容器时要扫描的包-->
    <context:component-scan base-package="com.fjnu"></context:component-scan>
    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="user" value="root"></property>
        <property name="password" value="gcm124126"></property>
    </bean>
</beans>

测试类

这时使用locations指定配置文件位置

/**
 * 使用junit进行单元测试,测试配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    IAccountService as;
    IAccountDao ad =null;

    @Test
    public void testFindAll(){
        // 执行方法
        List<Account> accounts = as.findAllAccount();
        for(Account account : accounts){
            System.out.println(account);
        }
    }
    @Test
    public void testFindOne(){
        Account account=as.findAccountById(1);
        System.out.println(account);
    }@Test
    public void testSave(){
        Account account = new Account();
        account.setName("test");
        account.setMoney(12345f);
        as.saveAccount(account);
    }
    @Test
    public void testUpdate(){
        Account account = as.findAccountById(4);
        account.setMoney(23456f);
        as.updateAccount(account);
    }
    @Test
    public void testDelete(){
        as.deleteAccount(4);
    }

}

动态代理

改变消费者直接与生产者购买产品的方式,转变为与代理购买,由代理负责与生产者购买。

基于子类的动态代理(cglib)

动态代理
           特点:字节码谁用谁创建,谁用谁加载
           作用:不修改源码的基础上对方法增强
           分类
               基于接口的动态代理
               基于子类的动态代理
           基于子类的动态代理:
               涉及的类:Enhancer
               提供者:第三方库cglib库
           如何创建代理对象:
               使用Enhancer中的create方法
           创建代理对象的要求:
               被代理类不能是最终类
           create方法的参数:
               class:字节码
                   它是指定被代理对象的字节码
               callBack:用于提供增强代码
                   它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类(但不是必须的)
                   此接口的实现类都是谁用谁写
                   我们一般写的都是该接口的子接口实现类:MethodInterceptor

消费者

/**
 * 模拟一个消费者
 */
public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             * 以上三个参数和基于接口的动态代理中Invoke方法的参数是一样的
             * @param methodProxy 当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                // 提供增强的代码
                Object returnValue = null;
                // 1.获取方法执行的参数
                Float money = (Float)args[0];
                if ("saleProduct".equals(method.getName())){
                    returnValue = method.invoke(producer,money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(12000f);
    }
}

生产者

public class Producer{
    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售商品,并拿到钱..."+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱..."+money);
    }
}

情景:消费者12000元跟代理购买产品

生产者实际收到金额

在这里插入图片描述

基于接口的动态代理

动态代理
 特点:字节码谁用谁创建,谁用谁加载
 作用:不修改源码的基础上对方法增强
 分类
     基于接口的动态代理
     基于子类的动态代理
 基于接口的动态代理:
     涉及的类:Proxy
     提供者:JDK官方
 如何创建代理对象:
     使用Proxy中的newProxyInstance方法
 创建代理对象的要求:
     被代理类最少要实现一个接口,如果没有则不能使用
 newProxyInstance方法的参数:
     classLoader
         它是用于加载代理对象字节码的,和被代理对象的使用相同的类加载器。固定写法
     class[]
         它是用于让代理对象和被代理对象有相同的方法。
     InvocationHandler
         它是让我们写如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类(但不是必须的)
         此接口的实现类都是谁用谁写

消费者

public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();

        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(), new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy     代理对象的引用
                     * @param method    当前执行的方法
                     * @param args      当前执行方法所需的参数
                     * @return          和被代理对象有相同的返回值
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        // 提供增强的代码
                        Object returnValue = null;
                        // 1.获取方法执行的参数
                        Float money = (Float)args[0];
                        if ("saleProduct".equals(method.getName())){
                            returnValue = method.invoke(producer,money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}

生产者

public class Producer implements IProducer{
    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售商品,并拿到钱..."+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱..."+money);
    }
}

情景:消费者10000元购买产品

生产者实际收到金额:

在这里插入图片描述

AOP

AOP(xml)

spring中基于xml的AOP配置步骤
    1.把通知的Bean也交给spring管理
    2.使用aop:config标签表明开始AOP的配置
    3.使用aop:aspect标签表明配置切面
            id属性: 给切面提供一个唯一标识
            ref属性: 指定通知类bean的id
    4.在aop:aspect标签的内部使用对应标签来配置通知的类型
        我们现在实例是让printLog方法在切入点方法执行前执行:所以是前置通知
        aop:before:表示配置前置通知
            method属性:用于指定Logger类中哪个方法是前置通知
            pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层哪些方法增强
      切入点表达式的写法:
            关键字:execution(表达式)
            表达式:
                访问修饰符   返回值   包名.包名...类名.方法名(参数列表)
            标准的表达式写法:
                public void cn.fjnu.service.Impl.AccountServiceImpl.saveAccount();
            访问修饰符可以省略
                    void cn.fjnu.service.Impl.AccountServiceImpl.saveAccount();
            返回值可以使用通配符,表示任意返回值
                * cn.fjnu.service.Impl.AccountServiceImpl.saveAccount();
            包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
            * *.*.*.*.AccountServiceImpl.saveAccount()
            包名可以使用..表示当前包及其子包(任意包下的只要有一个AccountServiceImpl,都会被增强)
            * *..AccountServiceImpl.saveAccount()
            类名和方法名都可以使用*来实现通配
            * *..*.*()
            参数列表:
                可以直接写数据类型:
                    基本类型直接写名称   int
                    引用类型写包名.类名的方式 java.lang.String
                可以使用通配符表示任意类型,但是必须有参数
                可以使用..表示有无参数均可,有参数可以是任意类型
            全通配写法:
                * *..*.*(..)
            实际开发中切入点表达式的通常写法:
                切到业务层实现类下的所有方法
                    * cn.fjnu.service.impl.*.*(..)

配置文件:

<?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: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/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置spring的Ioc,把Service对象配置进来-->
    <bean id="accountService" class="cn.fjnu.service.Impl.AccountServiceImpl"></bean>


    <!--配置Logger类-->
    <bean id="logger" class="cn.fjnu.utils.Logger"></bean>

    <!--配置AOP-->
    <aop:config>
        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知类型且建立通知方法和切入点方法的关联-->
            <aop:before method="printLog" pointcut="execution(* *..*.*(..))">
            </aop:before>
        <!--
    		后置通知<aop:after-returning method=""></aop:after-returning>
            异常通知<aop:after-throwing method=""></aop:after-throwing>
            最终通知<aop:after method=""></aop:after>
            环绕通知<aop:around method=""></aop:around>
		-->
        </aop:aspect>
    </aop:config>
</beans>

前置通知示例:

Logger.java

/**
 * 用于记录日志的工具类,里面提供了公共的代码
 */
public class Logger {
    /**
     * 用于打印日志:计划让其在切入点方法之前执行(切入点方法就是业务层方法)
     */
    public void printLog(){
        System.out.println("Logger类中的printLog方法开始记录日志了。。。");
    }
}

测试AOP配置

/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();
        as.updateAccount(1);
        as.deleteAccount();
    }
}

运行结果:

在这里插入图片描述

AOP(注解实现)

配置文件:

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置spring中创建容器时要扫描的包-->
    <context:component-scan base-package="cn.fjnu"></context:component-scan>
    <!-- 配置spring开启注解AOP支持 -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>

Logger.java

@Component("logger")
@Aspect // 表示当前类是一个切面类
public class Logger {
    @Pointcut("execution(* cn.fjnu.service.Impl.*.*(..))")
    private void pt1() {}
    /**
     * 前置通知
     */
    //@Before("pt1()")
    public void beforePrintLog(){
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了。。。");
    }

    /**
     * 后置通知
     */
    //@AfterReturning("pt1()")
    public void afterReturningPrintLog(){
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了。。。");
    }

    /**
     * 异常通知
     */
    //@AfterThrowing("pt1()")
    public void afterThrowingPrintLog(){
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了。。。");
    }

    /**
     * 最终通知
     */
    //@After("pt1()")
    public void afterPrintLog(){
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了。。。");
    }
    /**
     * 环绕通知
     */
    @Around("pt1()")
    public Object aroundPrintLog(ProceedingJoinPoint pjp){
        Object rtValue = null;
        try{
            Object[] args = pjp.getArgs();//得到方法执行所需参数
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。前置");
            rtValue = pjp.proceed();//明确调用业务层方法(切入点方法)
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。后置");
            return rtValue;
        }catch (Throwable t){
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。异常");
            throw new RuntimeException(t);
        }finally {
            System.out.println("Logger类中的aroundPrintLog方法开始记录日志了。。。最终");
        }
    }
}

AccountServiceImpl.java

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    public void saveAccount() {
        System.out.println("执行了保存");
        int i=1/0;	//定义了一个异常
    }

    public void updateAccount(int i) {
        System.out.println("执行了更新"+i);
    }

    public int deleteAccount() {
        System.out.println("执行了删除");
        return 0;
    }
}

测试AOP(环绕通知)

/**
 * 测试AOP的配置
 */
public class AOPTest {
    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountService as = (IAccountService)ac.getBean("accountService");
        //3.执行方法
        as.saveAccount();

    }
}

运行结果:

在这里插入图片描述

jdbcTemplate(jdbc模板类)

配置文件:

<?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">
    <!-- 配置账户的持久层 -->
    <bean id="accountDao" class="com.fjnu.dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <!--配置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.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="gcm124126"></property>
    </bean>
</beans>

实现方式:

  1. 继承JdbcDaoSupport
  2. 注入JdbcTemplate

demo1:new一个JdbcTemplate直接使用方法:

public class JdbcTemplateDemo1 {

    public static void main(String[] args) {
        //准备数据源:spring内置数据源
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.cj.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&serverTimezone=UTC");
        ds.setUsername("root");
        ds.setPassword("gcm124126");
        //1.创建JdbcTemplate对象
        JdbcTemplate jt = new JdbcTemplate();
        //给jt配置数据源
        jt.setDataSource(ds);
        //2.执行操作
        jt.execute("insert into account(name,money)values ('ccc',1000)");
    }
}

demo2:配置文件中JdbcTemplate注入,从容器中获取

public class JdbcTemplateDemo2 {

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

    }
}

demo3:jdbcTemplate的CRUD操作

/**
 * jdbcTemplate的CRUD操作
 */
public class JdbcTemplateDemo3 {

    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
        //3.执行操作
        //保存
//        jt.update("insert into account(name,money) values (?,?)","eee",3333f);
        //更新
//        jt.update("update account set name=?,money=? where id=?","test",4567,11);
        //删除
//        jt.update("delete from account where id=?",11);

        //查询所有
        List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),"1000");
        //List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),"1000");
        for (Account account : accounts){
            System.out.println(account);
        }
        //查询一个
      /*  List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
        System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));*/
        //查询返回一行一列(使用聚合函数,但是不加group子句)
        Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f );
        System.out.println(count);
    }

}

/**
 * 定义Account的封装策略
 */
class AccountRowMapper implements RowMapper<Account>{
    /**
     * 把结果集中的数据封装到Account中,然后由Spring把每个Account加到集合中
     * @param resultSet
     * @param i
     * @return
     * @throws SQLException
     */
    public Account mapRow(ResultSet resultSet, int i) throws SQLException {
        Account account = new Account();
        account.setId(resultSet.getInt("id"));
        account.setName(resultSet.getString("name"));
        account.setMoney(resultSet.getFloat("money"));
        return account;
    }
}

demo4:通过service层调用

public class JdbcTemplateDemo4 {

    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountDao accountDao = ac.getBean("accountDao", AccountDaoImpl.class);
        Account account = accountDao.findAccountById(1);
        System.out.println(account);
        account.setMoney(10000f);
        accountDao.updateAccount(account);
    }
}

事务管理

自定义事务实现

配置文件:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!--告知spring容器在创建容器时要扫描的包-->
    <context:component-scan base-package="com.fjnu"></context:component-scan>
    <bean id="accountService" class="com.fjnu.service.impl.AccountServiceImpl">
        <!--注入dao-->
        <property name="accountDao" ref="accountDao"></property>
        <!--注入事务管理器-->
        <property name="txManager" ref="txManager"></property>
    </bean>

    <bean id="accountDao" class="com.fjnu.dao.impl.AccountDaoImpl">
        <!--注入QueryRunner-->
        <property name="runner" ref="runner"></property>
        <!--注入connectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <!--
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        -->
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="user" value="root"></property>
        <property name="password" value="gcm124126"></property>
    </bean>

    <!--配置Connection的工具类 ConnectionUtils-->
    <bean id="connectionUtils" class="com.fjnu.utils.ConnectionUtils">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务管理器-->
    <bean id="txManager" class="com.fjnu.utils.TransactionManager">
        <!--注入connectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

事务管理类:

/**
 * 和事务管理相关的工具类,包含了开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
    /**
     * 开启事务
     */
    public void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 提交事务
     */
    public void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 回滚事务
     */
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    public void release(){
        try {
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

数据库连接类:

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程绑定
 */
public class ConnectionUtils {

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection(){

        try{
            //1.先从ThreadLocal上获取
            Connection conn = tl.get();
            //2.判断当前线程上是否有连接
            if (conn==null){
                //3.从数据源中获取一个连接存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //4.返回房钱线程上的连接
            return conn;
        }catch(Exception e){
            throw new RuntimeException(e);
        }

    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}

示例:

方法:

@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    private TransactionManager txManager;

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

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public List<Account> findAllAccount() {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            List<Account> accounts = accountDao.findAllAccount();
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return accounts;
        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.释放连接
            txManager.release();
        }

    }

    public Account findAccountById(Integer accountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            Account account = accountDao.findAccountById(accountId);
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return account;
        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.释放连接
            txManager.release();
        }

    }

    public void saveAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.saveAccount(account);
            //3.提交事务
            txManager.commit();

        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
        }finally {
            //5.释放连接
            txManager.release();
        }

    }

    public void updateAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.updateAccount(account);
            //3.提交事务
            txManager.commit();

        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
        }finally {
            //5.释放连接
            txManager.release();
        }
        accountDao.updateAccount(account);
    }

    public void deleteAccount(Integer accountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.deleteAccount(accountId);
            //3.提交事务
            txManager.commit();
        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
        }finally {
            //5.释放连接
            txManager.release();
        }

    }

    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作

            //2.1 根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2 根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3 转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4 转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5 更新转出账户
            accountDao.updateAccount(source);

            int i =1/0;

            //2.6 更新转入账户
            accountDao.updateAccount(target);

            //3.提交事务
            txManager.commit();


        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
            e.printStackTrace();
        }finally {
            //5.释放连接
            txManager.release();
        }

    }
}

初始:

在这里插入图片描述

测试:

public void testTransfer(){
    as.transfer("aaa","bbb",100f);
}

控制台输出:

出现异常

在这里插入图片描述

结果:

在这里插入图片描述

结论:自定义的事务管理,代码臃肿,依赖性也比较强。

事务(xml版)

spring中基于xml的声明事务控制配置步骤
    1.配置事务管理器
    2.配置事务的通知
            此时我们需要导入事务的约束 tx的名称空间和约束
            使用tx:advice标签配置事务通知
                属性:
                    id:给事务通知起一个唯一标志
                    transaction-manager:给事务通知提供一个事务管理器引用
    3.配置AOP中的通用切入点表达式
    4.建立事务通知和切入点表达式的对应关系
    5.配置事务的属性
            是在事务的通知tx:advice标签的内部

配置文件:

<?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: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/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">
    <!--配置业务层-->
    <bean id="accountService" class="com.fjnu.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!-- 配置账户的持久层 -->
    <bean id="accountDao" class="com.fjnu.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="gcm124126"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--配置事务的属性
            isolation=用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别
            propagation=用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS
            read-only= 用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写
            timeout= 用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
            rollback-for= 用于指定一个异常,当产生该异常时事务回滚,产生其他异常时事务不回滚。没有默认值,表示任何异常都回滚。
            no-rollback-for= 用于指定一个异常,当产生该异常时事务不回滚,产生其他异常时事务回滚,没有默认值,表示任何异常都回滚。
        -->
        <tx:attributes>
            <tx:method name="transfer" propagation="REQUIRED" read-only="false" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>
    <!--配置AOP-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.fjnu.service.impl.*.*(..))"/>
        <!--建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
</beans>

示例:

转账的方法:

public void transfer(String sourceName, String targetName, Float money) {
    //1.根据名称查询转出账户
    Account source = accountDao.findAccountByName(sourceName);
    //2.根据名称查询转入账户
    Account target = accountDao.findAccountByName(targetName);
    //3.转出账户减钱
    source.setMoney(source.getMoney()-money);
    //4.转入账户加钱
    target.setMoney(target.getMoney()+money);
    //5.更新转出账户
    accountDao.updateAccount(source);
    int i=1/0;	//定义一个异常
    //6.更新转入账户
    accountDao.updateAccount(target);

}

执行测试

@Test
public void testTransfer(){
    as.transfer("aaa","bbb",100f);
}

初始:

在这里插入图片描述

执行:

在这里插入图片描述

结果:

在这里插入图片描述

结论:对转账方法实现事务管理,统一提交,而不是分别提交。

事务(xml+注解 常用)

spring中基于注解的声明事务控制配置步骤
    1.配置事务管理器
    2.开启spring对注解事务的支持
    3.在需要事务支持的地方使用@Transactional注解

配置文件:

<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/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
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.fjnu"></context:component-scan>
    <!--配置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.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="gcm124126"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--开启spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

AccountServiceImpl.java

@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;

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

    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }

    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public void transfer(String sourceName, String targetName, Float money) {
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出账户
        accountDao.updateAccount(source);
        int i=1/0;
        //6.更新转入账户
        accountDao.updateAccount(target);

    }
}

初始:

在这里插入图片描述

执行方法:

在这里插入图片描述

结果:

在这里插入图片描述

事务(纯注解实现)

定义一个配置类:SpringConfiguration.java

/**
 * spring配置类,相当于bean.xml
 */
@Configuration
@ComponentScan("com.fjnu")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}

子配置类:JdbcConfig.java

/**
 * 和连接数据库相关的配置类
 */
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    /**
     * 创建JdbcTemplate
     * @param dataSource
     * @return
     */
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

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

子配置类:TransactionConfig.java

/**
 * 和事务相关的配置类
 */
public class TransactionConfig {
    /**
     * 用于创建事务管理器对象
     * @param dataSource
     * @return
     */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

事务模板类实现

配置文件:

<?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">
    <!--配置业务层-->
    <bean id="accountService" class="com.fjnu.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao">
        </property>
        <property name="transactionTemplate" ref="transactionTemplate"></property>
    </bean>
    <!-- 配置账户的持久层 -->
    <bean id="accountDao" class="com.fjnu.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&amp;serverTimezone=UTC"></property>
        <property name="username" value="root"></property>
        <property name="password" value="gcm124126"></property>
    </bean>
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务模板对象-->
    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
</beans>

AccountServiceImpl.java

public void transfer(final String sourceName, final String targetName, final Float money) {
    transactionTemplate.execute(new TransactionCallback<Object>() {
        public Object doInTransaction(TransactionStatus transactionStatus) {
            //1.根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //3.转出账户减钱
            source.setMoney(source.getMoney()-money);
            //4.转入账户加钱
            target.setMoney(target.getMoney()+money);
            //5.更新转出账户
            accountDao.updateAccount(source);
            /*int i=1/0;*/
            //6.更新转入账户
            accountDao.updateAccount(target);
            return null;
        }
    });

}

AccountServiceImpl.java

public void transfer(final String sourceName, final String targetName, final Float money) {
    transactionTemplate.execute(new TransactionCallback<Object>() {
        public Object doInTransaction(TransactionStatus transactionStatus) {
            //1.根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //3.转出账户减钱
            source.setMoney(source.getMoney()-money);
            //4.转入账户加钱
            target.setMoney(target.getMoney()+money);
            //5.更新转出账户
            accountDao.updateAccount(source);
            /*int i=1/0;*/
            //6.更新转入账户
            accountDao.updateAccount(target);
            return null;
        }
    });

}


共勉: 很多孩子在幼年时期有着波澜壮阔的梦想,却最终活成了最普通的模样。 他们做着普通的工作,过着普通的生活,没有出类拔萃,没有万众瞩目,甚至儿时的一点天赋与灵气,也已经在人生的风雨与岁月的磨砺中泯然于众。 但是,这个世界不是只有星光最亮的星星才值得挂在天上。每个人都是一颗独一无二的星辰,站在自己的位置,发着自己的光。

在CSDN分享自己的学习笔记。如果有不足之处,或者说错的地方,请多指教!
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值