Skip to content

一套非常实用的Spring教程系列,适合初学者学习使用,后期会添加源码阅读等

Notifications You must be signed in to change notification settings

wlwqq/Spring-jiaocheng

Repository files navigation

Spring实用教程


声明:这一套教程为现在火热的javaEE框架Spring的教程,其中Spring版本为5.x,教程所用的开发工具为IDEA2018版本,代码实例都会上传到github仓库,喜欢的可以star一下。

目录

[TOC]

1.Spring概述

1.1 spring的简介

Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:

反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring

MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多

著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

1.2 spring的结构图

对图片做一个解释,从下往上看

Test为Spring整合junit单元测试;

Core Container为spring框架的第一个特点,它的核心容器,包括beans,core,context,spel

AOP和Aspect两个一起是spring的第二个特点,面向切面编程

Data Access/Integration表示spring中与数据库连接的部分

Web表示spring中关于web前端的相关部分

总结:对于spring的学习,请抓住两点,第一,spring中的核心容器的学习,即IOC和DI,第二,面向切面编程的学习,即AOP,这一套spring教程的重点也在这两方面。

2.Spring中的IOC和DI

2.1 什么是IOC和DI

IOC反转控制,什么是反转控制呢?一开始学java的时候都听说过一句话,缺什么对象就new一个对象,但是在软件工程的中,有一个原则叫低耦合高内聚,当我们new一个对象的时候,我们就开始依赖这个对象了,这样就不符合低耦合高内聚的原则。

这里我们的思想来一个小的转变,以前,我们主动new对象,这种方式称为主动控制,因为我们new的对象,我们控制这个对象。那么什么叫反转控制呢?就是说现在我们不new对象了,让别人去new,那么这个控制权就给别人了,这种方式就是反转控制。那么这个别人是谁呢?就是大名鼎鼎的spring的IOC。这就是IOC的由来。

说完了IOC,DI又是什么呢?DI全名依赖注入,试想一下,new的对象里面的属性参数谁来给呢?我们自己new对象的话可以用set方法给对不对,我们交给Spring的IOC去new之后,谁来给呢?肯定是Spring来给,这种给的过程就叫依赖注入

总结:Spring的IOC就是一个容器,这个容器是key-value形式的,就是一个map,我们需要对象的时候就用key从IOC容器中拿对象,对象在创建过程中,属性的声明就叫依赖注入。下面两张图就更加形象了,第二张图中的工厂就相当于Spring中的IOC容器。不多说了,这应该是最简单易懂的IOC和DI的介绍了。

2.2 IDEA实现IOC和DI(xml版本代码在springIOCxml中)

(1)创建maven工程,并且导入spring5.x版本

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

(2)编写service层和dao层

具体代码请看仓库中springIOCxml项目

(3)设置IOC容器(重点)

1.在resource目录下新建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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    
</beans>

2.以上步骤我们就有了一个IOC容器了,那么如何在容器中存储对象呢

<bean id="accountService" class="com.wanglei.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="com.wanglei.dao.impl.AccountDaoimpl"></bean>

bean标签用来存储对象,有三个属性:

id指定bean的名称,唯一,用来从容器中获取该对象

class给定全类名,spring用反射给你new对象

scope指定对象的作用范围,值有singleton(单例)、prototype(多例)、request、response、global session

其实有5个,但是另外两个我觉得不是特别重要,以后源码阶段再提

(4)DI依赖注入(重点)

有了对象还不行,如果对象中要注入属性怎么办?主要有2种注入方式

1.构造函数注入

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
	<constructor-arg name="name" value="张三"></constructor-arg>
</bean>

顾名思义,必须这个类中有构造函数才可以用

constructor-arg标签用来注入数据,有2类属性,第一类:index、type、name、第二类:value、ref、

第一类用来找谁赋值

第二类用来赋什么值

比方说上面的代码,给name赋值为张三

2.set方法注入

<bean id="accountService" class="com.wanglei.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
</bean>

顾名思义,set方法注入,必须在这个类中有set方法

property标签用来注入数据,同样是2类属性,第一类name,第二类 value、ref

这里name指的是set方法后的内容,这一块可以查看springIOCxml项目中AccountServiceImpl中的set方法,一看就懂,ref用来指向本容器中的对象。

(5)使用IOC容器

在main函数中使用一下我们的IOC容器

public static void main(String[] args) {
        //1.获取核心容器
        ApplicationContext ioc = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取accountService对象
        AccountService accountService = (AccountService) ioc.getBean("accountService");
        //3.调用findAccount()方法
        accountService.findAccount();
    }

效果如下

至此Spring的IOC和DI的xml配置版本就完成了

2.3 IDEA实现IOC和DI(注解版,代码在springiocAnnotation中)

上面对于springIOC和DI的xml配置也可以完全使用注解来代替,注解和xml只是两种不同的形式而已,但是他们的功能是完全一样的。

那么如何学习springIOC的注解呢?首先要清楚Spring的IOC在干两件事,第一,把对象注入到容器中;第二,给对象注入依赖。在xml中bean标签表示把对象注入到容器中;constract-argproperty表示将依赖注入到对象中,一个是利用构造函数,一个是利用set方法。

那么注解版也一样咯,肯定有某个注解将对象注入到容器中,又有某个注解将依赖注入。下面带着这样的思路配置springIOC的注解版

(1)创建项目导入maven依赖

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

(2)书写service层和dao层

在本仓库中查看springiocAnnotation项目,程序运行的逻辑与之前一样,service调用dao。

(3)调整bean.xml

第一:导入context命名空间

xmlns:context="http://www.springframework.org/schema/context"

第二:开启注解扫描功能

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

(4)使用注解

开头分析过,无非是两种注解,一个注入对象的,一个依赖注入的,这里先看第一个

@Component

@Component(value = "accountService")
public class AccountServiceImpl implements AccountService {

表示把这个类产生的对象放到容器中,value=“accountService”表示这个对象的id是accountService

,还有三个衍生的注解:@Controller、@Service、@Repository:意思很明显,三层对应的对象,这三个只是给看的,其实没什么区别。那么我们的项目就应该这样注解了

@Service(value = "accountService")
public class AccountServiceImpl implements AccountService {

@Repository(value = "accountDao")
public class AccountDaoimpl implements AccountDao {

关于依赖注入的种类较多,这里先分析再解释,一个对象中也许有基本属性,比如name、age等,也许有对象树属性,比如这个例子的service中有dao对象,那么属性的类型不同,注入方式就不一样

@Value

用来给基本属性注入的,value=“王磊”表示给这个属性注入王磊

@Autowired、@Qualifier、@Resource

这三个分别用来注入对象的,用法如下

@Autowired
private AccountDao accountDao;
    
@Autowired
@Qualifier(value = "accountDao")
private AccountDao accountDao;

@Resource(name = "accountDao")
private AccountDao accountDao;

解释一下,@Autowired表示根据类型自动注入、@Qualifier表示在@Autowired基础上根据对象id注入、@Resource表示根据对象id注入。

@Scope

改变作用范围,使用如下:

@Service(value = "accountService")
@Scope(value = "singleton")
public class AccountServiceImpl implements AccountService {

(5)彻底告别bean.xml文件

@Configuration

这个注解标记在一个类上,表示这个类是一个bean.xml的替身,有了这个注解,就可以删除bean.xml文件了

@Configuration
public class SpringConfiguration {

@ComponentScan

这个注解与bean.xml中<context:component-scan base-package="com.wanglei">意思相同

用法如下

@Configuration
@ComponentScan(value = "com.wanglei")
public class SpringConfiguration {

@Bean

写在方法上,表示将这个方法的返回值注入到容器中,这里写一个新的配置类,就是关于jdbc的配置类,当我们的dao需要一个QueryRunner,而QueryRunner需要一个连接池的时候,依赖关系就变成了这样:service调用dao,dao调用QueryRunner,QueryRunner调用c3p0连接池。

(1)导入maven设置如下

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

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>

        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

(2)@Bean注入QueryRunner

    @Bean(name = "queryRunner")
    @Scope(value = "prototype")
    public QueryRunner getQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

(3)@PropertySource("classpath:jdbcConfig.properties")和@Bean注入datasource,因为QueryRunner需要它

@PropertySource("classpath:jdbcConfig.properties")

表示引入jdbcConfig.properties文件进来可以i读取属性

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

    @Bean(name = "queryRunner")
    @Scope(value = "prototype")
    public QueryRunner getQueryRunner(@Autowired DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    @Bean(name = "dataSource")
    public DataSource getDataSource(){
        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);
        }
    }

@Import

导入其他配置类到主配置类,用法如下

@Configuration
@ComponentScan(value = "com.wanglei")
@PropertySource("classpath:jdbcConfig.properties")
@Import(JdbcConfiguration.class)
public class SpringConfiguration {

}

(6)测试我们的注解版IOC

public class Main {
    public static void main(String[] args) {
        //1.获取核心容器
        ApplicationContext ioc = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.获取accountService对象
        AccountService accountService = (AccountService) ioc.getBean("accountService");
        //3.调用findAccount()方法
        accountService.findAccount();
    }
}

3.Spring中的单元测试

在上面的两个项目中关于IOC容器的测试我都是采用直接写主函数测试,这样不是特别科学,真正科学正确的方式应该是在web阶段接触到的junit测试,但是spring中的junit测试有点不同,在spring中使用junit的过程如下:分别在springIOCxml项目和springiocAnnotation项目中演示。

3.1 导入junit相关的maven依赖

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

3.2 新建测试类AccountTest.java

在test包下新建AccountTest.java

3.3 @RunWith注解和@ContextConfiguration

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

}

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

3.4 编写测试方法测试

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

    @Autowired
    private AccountService accountService;

    @Test
    public void test1(){
        accountService.findAccount();
    }
}

3.5 总结

之所以要spring整合junit测试,只有一个目的,在写测试方法的时候不需要写获取核心容器,从核心容器中获取对象这两行代码了,其他的没什么区别。

4.Spring中的AOP

Spring中的AOP是Spring的第二大特性,AOP(面向切面编程),什么叫做面向切面编程呢?打比方说,为什么要打比方?。。。。。。我们的项目中有许多方法,当我需要在每个方法执行的时候打印一下日志该怎么办?最简单的方式就是在每个方法内部写上打印日志的代码,这也就意味着所有的方法我们都要写重复的代码,并且有一天我需要改代码的时候所有的都要改,麻烦而且不符合软件工程设计模式的规范。

所以引入了AOP编程。

4.1 SpringAOP的相关概念

以上面的需求为例,给每个方法添加日志

连接点(JointPoint):指的是项目中的所有方法,我们可以为每一个方法都加上写日志的代码,所以每一个候选者都是连接点

切入点(pointcut):那我们肯定不是每个方法都要加日志,那些需要处理的连接点我们称作切入点

通知(Advice):写日志的代码就叫做通知,意思就是具体增强的那部分代码

切面(Aspect)切入点+通知=切面

那么AOP面向切面编程的概念就呼之欲出了,把通知写到切入点里面去这样的过程就叫AOP,而通知+切入点=切面,所以又叫做面向切面编程。

4.2 IDEA实现AOP(xml版本,代码为SpringAOPxml项目)

(1)创建maven项目,导入依赖

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

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>

(2)写Service代码(切入点)和通知代码Logger

具体查看仓库SpringAOPxml项目,明确我们的目的,当AccountService中的方法执行,就要打印日志,意思就是把Logger中的方法搞到AccountService中,怎么搞呢?往下看

(3)基于xml配置AOP

之前的IOC配置也是需要的呦,回顾之前的介绍AOP是什么?切入点+通知对吧,其实也就是切面+通知,那么AOP的配置也就是2点:切面+通知

<!--配置ioc-->
<bean id="accountService" class="com.wanglei.service.impl.AccountServiceImpl"></bean>
<bean id="log" class="com.wanglei.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
    <aop:aspect id="LogAdvice" ref="log">
        <aop:before method="printLog" pointcut="execution(* com.wanglei.service.impl.*.*						(..))" >
        </aop:before>
    </aop:aspect>
</aop:config>

(4)测试

直接写主方法测试了

public class Main {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountService as = (AccountService) ac.getBean("accountService");
        as.saveAcoount();
    }
}

(5)Advice类型

上面的例子都是在方法执行之前执行,如果需要在方法执行其他时间执行怎么办呢

<aop:config>
        <aop:pointcut id="accountServicePoint" expression="execution(* com.wanglei.service.impl.*.*(..))"></aop:pointcut>
        <aop:aspect id="logAdvice" ref="log">
            <!--前置通知-->
            <aop:before method="printLog1" pointcut-ref="accountServicePoint"></aop:before>
            <!--后置通知-->
            <aop:after-returning method="printLog2" pointcut-ref="accountServicePoint"></aop:after-returning>
            <!--异常通知-->
            <aop:after-throwing method="printLog3" pointcut-ref="accountServicePoint"></aop:after-throwing>
            <!--最终通知-->
            <aop:after method="printLog4" pointcut-ref="accountServicePoint"></aop:after>

        </aop:aspect>
</aop:config>

4.3 IDEA实现AOP(注解版,项目SpringAOPAnnotation)

(1)新建maven项目,导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>

(2)写Service和Advice代码

具体查看仓库SpringAOPannotation项目

(3)基于注解配置SpringAOP

xml配置时注意切面和通知,那么注解配置也是解决这两个问题

bean.xml中配置如下

    <!--IOC注解扫描-->
    <context:component-scan base-package="com.wanglei"></context:component-scan>
    <!--AOP注解扫描-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

加上IOC的注解(不在解释了)

AOP注解

@Aspect:表明当前类是一个切面类

@Pointcut("execution( com.itheima.service.impl..(..))")*:配置切入点表达式

@Component(value = "log")
@Aspect
public class Logger {

    @Pointcut(value = "execution(* com.wanglei.service.impl.*.*(..))")
    public void getPoint(){}

    public void printLog(){
        System.out.println("打印日志");
    }

    @Before(value = "getPoint()")
    public void printLog1(){
        System.out.println("打印日志1");
    }
    @AfterReturning(value = "getPoint()")
    public void printLog2(){
        System.out.println("打印日志2");
    }
    @AfterThrowing(value = "getPoint()")
    public void printLog3(){
        System.out.println("打印日志3");

    }
    @After(value = "getPoint()")
    public void printLog4(){
        System.out.println("打印日志4");
    }
}

(4)在main方法中测试

public class Main {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountService as = (AccountService) ac.getBean("accountService");
        as.saveAcoount();
    }
}

5.Spring JDBCTemplate对象

JdbcTemplate是一个Spring对象,与DButils差不多,这个对象的作用是和数据库进行交互

学习一个对象的三大步骤:(1)对象用来干什么,前面提到了(2)怎么创建对象(3)对象有什么方法

JdbcTemplate的运行过程是这样的:

JavaWeb阶段:操作对象(DButils中QueryRunner)====> 数据源(c3p0)====> 数据库(MySql)

SSM框架阶段:操作对象(JdbcTemplate)====> 数据源(Spring自带的DriverManager)====> 数据库(MySql)

5.1 JDBCTemplate快速入门

(1)创建项目,导入maven依赖

创建maven项目SpringJDBCTemplate

 <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1</version>
        </dependency>
</dependencies>

(2)按照SSM框架阶段:操作对象(JdbcTemplate)====> 数据源(Spring自带的DriverManager)====> 数据库(MySql)配置IOC容器

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://192.168.1.104:3306/database"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
</bean>

(3)编写主方法测试

    public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
        jt.execute("insert into account(name,money) values('wanglei',555)");
    }

5.2 JDBCTemplate对象的方法

public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jt = (JdbcTemplate) ac.getBean("jdbcTemplate");
        //1.没有占位符参数的
        jt.execute("insert into account(name,money) values('wanglei',555)");
        //2.带有占位符参数的增删改
        jt.update("insert into account(name,money) values(?,?)", "wqq", 888);
        jt.update("update account set name=?,money=? where id=?","liuyang",789,1);
        jt.update("delete from account where id=?", 2);
        //3.带有占位符参数的查询
        List<Account> list = jt.query("select * from account", new BeanPropertyRowMapper<>(Account.class));
        for (Account account:list)
        {
            System.out.println(account);
        }
    }

**new BeanPropertyRowMapper<>(Account.class)**其实是在对查询到的那一行数据封装成Account类型。

5.3 JDBCTemplate在dao层使用

(1)写好Dao层以及Dao的实现类

具体代码查看代码仓库,注意下面这一段,我们在依赖注入时,使用set注入

public class AccountDaoImpl implements AccountDao {

    private JdbcTemplate jt;

    public void setJt(JdbcTemplate jt) {
        this.jt = jt;
    }

(2)在IOC容器中配置Dao

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://192.168.1.104:3306/database"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="accountDao" class="com.wanglei.dao.impl.AccountDaoImpl">
        <property name="jt" ref="jdbcTemplate"></property>
    </bean>

(3)编写主方法测试

public class JdbcTempate2 {

    public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountDao ad = (AccountDao) ac.getBean("accountDao");
        Account account = ad.findByName("wqq");
        System.out.println(account);
    }
}

6.Spring中的事务控制

Spring中的事务控制采用转账的例子进行解释

6.1 准备代码

具体代码查看SpringTransactionXml仓库

(1)新建maven项目,导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
    </dependencies>

(2)编写service层和dao层,并且实现转账的业务逻辑

@Override
    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findByName(sourceName);
        Account target = accountDao.findByName(targetName);

        if (source.getMoney() < money){
            throw new RuntimeException("金额不足");
        }
        source.setMoney(source.getMoney() - money);
        target.setMoney(target.getMoney() + money);

        accountDao.updateAccount(source);
        accountDao.updateAccount(target);
    }

(3)在主方法中测试转账逻辑

public class TransactionTest {
    public static void main(String[] args) {
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        AccountService as = (AccountService) ac.getBean("accountService");
        as.transfer("liuyang","wanglei",100f);
    }
}

6.2 xml声明式事务

Spring中的事务控制是通过AOP来实现的,还记得AOP的步骤吗?AOP面向切面编程,我们首先有一个切面,其次有若干个切入点需要被加强,将切面和切入点关联起来编程就是AOP编程,那么在事务中就是这样体现的,我们首先有一个事务管理器(里面都是提交回滚等操作),其次有切入点(比如我们的增删改查、转账方法),将二者结合起来就是AOP事务了,结合的方式就是切入点表达式。

(1)在xml中导入aop和tx约束

(2)配置事务管理器

<bean id="transaction" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="database"></property>
</bean>

(3)配置事务的通知Advice

<!-- 配置事务的通知-->
<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="*" propagation="REQUIRED" read-only="false"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
</tx:advice>

(4)配置AOP,建立Advice和method的联系

  <!-- 配置aop-->
    <aop:config>
        <!-- 配置切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

(5)测试事务

在AccountServiceImpl的transfer方法中加上异常,测试后数据库事务回滚,不提交。

6.3 注解声明式事务

(1)新建maven项目SpringTransactionAnno(见代码仓)

(2)Spring注解事务的三大步骤

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


     -->
    <!-- 配置事务管理器 -->
    <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>

(3)使用@Transaction注解事务

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

    @Autowired
    private AccountDao accountDao;

    @Override
    public void findByName(String accountName) {
        accountDao.findByName(accountName);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)
    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findByName(sourceName);
        Account target = accountDao.findByName(targetName);

        if (source.getMoney() < money){
            throw new RuntimeException("金额不足");
        }
        source.setMoney(source.getMoney() - money);
        target.setMoney(target.getMoney() + money);

        accountDao.updateAccount(source);
        int i = 1/0;
        accountDao.updateAccount(target);
    }

(4)测试代码

6.4 完全去除bean.xml,复习之前的学习的知识!

(1)在config包下新建SpringConfigration.java代替bean.xml

@Configuration
@ComponentScan(value = "com.wanglei")
@Import(value = {JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbc.properties")
@EnableTransactionManagement
public class SpringConfig {
}

(2)新建JdbcConfig.java处理数据库相关的bean

public class JdbcConfig {

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

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

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

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

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

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

}

(3)新建TransactionConfig.java出来事务相关bean

public class TransactionConfig {

    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransaction(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

(4)主方法测试

public class TransactionTest {
    public static void main(String[] args) {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
        AccountService as = (AccountService) ac.getBean("accountService");
        as.transfer("liuyang", "wanglei",100f);
    }
}

具体代码见仓库中SpringTransactionAnno项目。

7.Spring5新特性

未完待续

About

一套非常实用的Spring教程系列,适合初学者学习使用,后期会添加源码阅读等

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages