文章目录
本文出现过的案例
Spring的概述
Spring的概念
Spring是分层的JavaSE/JavaEE应用full-stack的轻量级开源框架,以IOC(Inverse Of Control,控制反转)和AOP(Aspect Oriented Programming,面向切面编程)为核心,提供了表现层SpringMVC和持久层Spring JDBC以及业务层事务管理等众多企业级应用技术的支持,还可以整合众多第三方框架。
Spring的优势
- 方便解耦,简化开发
- AOP 编程的支持
- 声明式事务的支持
- 方便程序的测试
- 方便集成各种优秀框架
- 降低 JavaEE API 的使用难度
- Spring的Java 源码是经典学习范例(需要有一定的代码阅读能力)
Spring的体系结构
程序的耦合与解耦
什么是耦合
对象之间的依赖,方法之间的依赖
耦合的分类
内容耦合、公共耦合、外部耦合、控制耦合、标记耦合、数据耦合、非直接耦合
内聚与耦合
内聚标志着一个模块内部各个元素关联的紧密程度。程序讲究的是高内聚、低耦合。
通过jdbc代码分析程序的耦合与解耦
jdbc代码
public class JdbcDemo1 {
public static void main(String[] args) throws Exception{
//1.注册驱动
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver");
//2.获取连接
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/eesy","root","1234");
//3.获取操作数据库的预处理对象
PreparedStatement pstm = conn.prepareStatement("select * from account");
//4.执行SQL,得到结果集
ResultSet rs = pstm.executeQuery();
//5.遍历结果集
while(rs.next()){
System.out.println(rs.getString("name"));
}
//6.释放资源
rs.close();
pstm.close();
conn.close();
}
}
代码中存在的问题
(1)DriverManager.registerDriver(new com.mysql.jdbc.Driver());
注册驱动需要使用new关键字,一旦mysql包不在,将会出现编译器异常
(2)Class.forName(“com.mysql.jdbc.Driver”);
使用这种办法注册驱动,一旦需要换成别的数据库,又需要重新修改代码,不够方便
解决思路
通过工厂模式解耦,将对象通过配置文件配置好,程序启动的时候将需要的对象创建并存储到容器中,使用的时候直接从容器中取出。
使用工厂模式实现程序解耦
(1)使用配置文件配置service和dao
配置的内容: 唯一标识 + 全限定类名
(2)读取配置文件,获取全限定类名,反射创建对象
xml、properties
(3)在resource中新建bean.properties文件,并填写以下内容
accountService=com.cncs.service.impl.AccountServiceImpl
accountDao=com.cncs.dao.impl.AccountDaoImpl
(4)建立BeanFactory类,生产Bean对象
(5)改造Service和Clinet层
IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
private IAccountDao accountDao = (IAccountDao)BeanFactory.getBean("accountDao");
控制反转(IOC – Inverse of Control)
解耦时需要加载很多对象,对象被存储到容器中,容器是集合组成的,有List或者Map。因为有查找需要,选Map。所以在加载对象时
将存储对象的Map称之为容器。
工厂:从容器中取出指定对象的类。原来得到对象是由用户自己创建,现在是向容器索取,由主动变成被动,所以称为控制反转。
控制反转:
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。
Spring入门案例(基于xml配置)
Spring IOC
概念:将创建对象的权力交给Spring框架去做
作用:削减程序的耦合
bean标签
作用
- 用于配置对象让 spring 来创建的
- 默认情况下它调用的是类中的无参构造函数。
- 如果没有无参构造函数则不能创建成功。
属性
- id 给对象在容器中提供一个唯一标识。用于获取对象。
- class 指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
- scope 指定对象的作用范围。
- init-method 指定类中的初始化方法名称
- destroy-method 指定类中销毁方法名称
scope取值
- singleton 默认的,单例的
- prototype 多例的
- request WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
- session WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
- global session WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session
bean的作用范围和生命周期
(1)单例对象:scope="singleton"
作用范围
一个应用只有一个对象的实例。它的作用范围就是整个引用。
生命周期
- 对象出生:当应用加载,创建容器时,对象就被创建了。
- 对象活着:只要容器在,对象一直活着。
- 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
(2)多例对象:scope="prototype"
作用范围
每次访问对象时,都会重新创建对象实例。
生命周期
- 对象出生:当使用对象时,创建新的对象实例
- 对象活着:只要对象在使用中,就一直活着。
- 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
实例化 bean 的三种方式
使用默认无参构造函数
<bean id="accountService" class="com.cncs.service.impl.AccountServiceImpl"/>
spring 管理静态工厂-使用静态工厂的方法创建对象
创建静态工厂类,添加对象生产方法
public class StaticFactory {
public static IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
在beans.xml中配置静态工厂方法
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
spring 管理实例工厂-使用实例工厂的方法创建对象
创建实例工厂类
public class InstanceFactory {
public IAccountService getAccountService(){
return new AccountServiceImpl();
}
}
在beans.xml中配置实例工厂方法
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
Spring DI
概念:依赖关系的维护
注入的数据类型
- 基本类型、String
- 其他Bean
- 复杂类型、集合类型
注入的方式
通过构造方法注入
步骤
(1)重载构造函数
(2)使用标签:constructor-arg
属性
- type 构造函数的参数类型
- index 构造函数的参数索引,从0开始
- name 构造函数的参数名称
- value 为参数赋值
- ref 引用另一个bean对象
优势
获取bean对象后,注入数据是必须操作,否则对象无法创建成功
弊端
改变了bean对象的实例化方式,用不上的数据也必须注入
实例
方式一:使用构造函数注入-->
<!--<bean id="accountService" class="com.cncs.service.impl.AccountServiceImpl">-->
<!--<constructor-arg name="name" value="李思思"></constructor-arg>-->
<!--<constructor-arg name="age" value="19"></constructor-arg>-->
<!--<constructor-arg name="date" ref="nowTime"></constructor-arg>-->
<!--</bean>-->
通过set方法注入
需要在类中为成员变量添加注入的set方法
使用标签:property
属性
- name set方法后面的名称
- value 为参数赋值
- ref 引用另一个bean对象
优势
不改变bean对象实例化方式
弊端
如果有某个成员变量必须注入,使用set方式无法保证为每个成员变量都注入成功
实例
添加AccountService2类
package com.cncs.service.impl;
import com.cncs.service.AccountService;
import java.util.Date;
public class AccountServiceImpl2 implements AccountService {
private String name;
private int age;
private Date date;
public void setName(String name) {
this.name = name;
}
public void setAge(int age) {
this.age = age;
}
public void setDate(Date date) {
this.date = date;
}
@Override
public void save() {
System.out.println("name:" + name + ",age:" + age + ",date:" + date);
}
}
编写配置文件
<bean id="accountService2" class="com.cncs.service.impl.AccountServiceImpl2">
<property name="name" value="张益达"></property>
<property name="age" value="24"></property>
<property name="date" ref="nowTime"></property>
</bean>
<bean id="nowTime" class="java.util.Date"></bean>
使用p名称空间注入
略
注入集合属性
注入复杂数据实例
array、list、map、set、properties
创建AccountServiceImpl3类
package com.cncs.service.impl;
import com.cncs.service.AccountService;
import java.util.*;
public class AccountServiceImpl3 implements AccountService {
private String[] myStrs;
private List<String> myList;
private Map<String,String> myMap;
private Set<Integer> mySet;
private Properties myProperties;
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public void setMySet(Set<Integer> mySet) {
this.mySet = mySet;
}
public void setMyProperties(Properties myProperties) {
this.myProperties = myProperties;
}
@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(myProperties);
}
}
编写配置文件
<bean id="accountService3" class="com.cncs.service.impl.AccountServiceImpl3">
<property name="myStrs">
<array>
<value>aaa</value>
<value>bbb</value>
<value>ccc</value>
</array>
</property>
<property name="myList">
<list>
<value>CCC</value>
<value>BBB</value>
<value>DDD</value>
</list>
</property>
<property name="mySet">
<set>
<value>111</value>
<value>776</value>
<value>345</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="sss" value="111"></entry>
<entry key="aaa">
<value>gggg</value>
</entry>
</map>
</property>
<property name="myProperties">
<props>
<prop key="fff">jjj</prop>
<prop key="lll">iii</prop>
</props>
</property>
</bean>
其他
Spring中工厂的类结构图
BeanFactory 和ApplicationContext的区别
BeanFactory
- Spring容器中的顶层接口
- 使用的时候才创建
ApplicationContext
- 是BeanFactory的子接口
- 一读取配置文件,默认情况就会创建对象
ApplicationContext接口的实现类
- ClassPathXmlApplicationContext
- 它是从类的根路径下加载配置文件 推荐使用这种
- FileSystemXmlApplicationContext
- 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
- AnnotationConfigApplicationContext
- 当我们使用注解配置容器对象时,需要使用此类来创建 spring 容器。它用来读取注解。
Spring中基于注解的IOC
案例02:Spring–02基于注解的IoC
IoC常用注解
用于创建对象
-
@Component
作用
把资源交给spring来管理,和作用一样
属性
value 用于指定bean的id,默认是当前类名,且首字母小写
-
@Controller
一般用在表现层 -
@Service
一般用在业务层 -
@Repository
一般用在持久层
用于依赖注入
-
@Autowired
作用
用于依赖注入
注入原则
(1)根据数据类型从spring容器查找bean对象
(2)根据变量名称从spring容器查找bean对象
-
@Qualifier
作用
同上
注入原则
-
对类成员变量注入
(1)按照类中成员变量注入的基础上,再按照名称注入
(2)必须和Autowired配合使用
-
对方法参数变量注入
用在方法参数数据类型的前面,可以直接使用
举例
@Bean(name = "runner") @Scope("prototype") QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource) { return new QueryRunner(dataSource); }
属性
value 指定要注入的bean的id
-
-
@Resource
注入原则
按照name指定的bean的id直接注入,可以独立使用
属性
name 指定bean的id
-
@Value
作用
用于注入基本类型和String类型的数据
属性
value 为数据赋值、可以使用spring中的SpEL(Spring中的EL表达式)、SpEL写法(${表达式})
用于改变bean的作用范围
-
@Scope
作用
指定bean的作用范围
属性
singleton、prototype
和生命周期相关
-
@PostConstruct
作用
指定创建对象时初始化方法
-
@PreDestory
作用
指定对象销毁时的销毁方法
Spring和Junit整合
(1)导入spring整合junit的坐标:spring-test
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
(2)使用Junit提供的一个注解@Runwith替换原来的main方法,替换成spring提供的运行器
(3)使用@ContextConfiguration注解告知spring的运行器,spring和ioc创建是基于xml还是注解的,并说明位置
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:bean.xml"})
public class AccountTest {
基于注解的IoC配置实现账户的CRUD
纯注解方式用到的新注解
-
@Configuration
声明当前类是配置类
作用
指定当前类是一个配置类
扩展
让spring识别扫描的包为配置类的两种方式
- 创建AnnotationConfigApplicationContext对象时,将主配置类作为参数传入,其余配置类加上@Configuration
- 创建AnnotationConfigApplicationContext对象时,将所有配置类作为可变参数传入
-
@ComponentScan
声明要扫描的包
作用
通过注解指定spring在创建容器时要扫描的包
属性
value 指定要扫描的包
basePackage 指定要扫描的包(和value二选一)
eg:
@ComponentScan(basePackages = {"com.cncs"}) //声明spring创建容器时要扫描的包 public class SpringConfiguration { }
-
@Bean
声明此方法返回值是一个bean
作用
把当前方法返回值作为bean放入spring容器
属性
name
-
指定bean的id
-
默认值:当前方法返回值的名称
-
如果使用注解配置方法时,如果有参数,spring会从容器中查找对象,和AutoWired一样
-
eg:
@Bean(name = "runner") @Scope("prototype") QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource) { return new QueryRunner(dataSource); }
-
-
@Import
声明要导入的子配置类
作用
- 导入其它配置类
- 使用了@Import的类为主配置类,导入的配置是子配置类
属性
value 指定其他配置类的字节码
eg
@Import({JdbcConfig.class}) @ComponentScan(basePackages = {"com.cncs"}) //声明spring创建容器时要扫描的包 public class SpringConfiguration { }
-
@PropertySource
声明使用properties文件的路径
作用
指定properties文件的位置
属性
value
- 指定文件路径和名称
- 关键字:classpath
- 有多级包存在的情况书写方式:
classpath:config/spring/jdbcConfig.properties
-
@Runwith
作用
替换原main方法,可以让junit提供的main帮助创建容器
-
@ContextConfiguration
作用
告知spring的运行器,spring和ioc是基于xml还是annotation,并说明位置
属性
- locations 指定xml文件的位置,加上classpath关键字,表示在类路径下
- classes 指定注解类的位置
eg
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfiguration.class}) public class AnnoConfigTest {
案例03:Spring–03基于注解的IoC配置实现账户的CRUD
基于XML的IoC配置实现账户的CRUD
案例04:Spring–04基于XML的IoC配置实现账户的CRUD
Java动态代理
动态代理概念
代理模式
在不修改代理对象的方法的基础上,通过扩展代理类,对方法进行增强和扩展
动态代理
代理对象的字节码随用随加载
动态代理与静态代理区别
动态代理随用随加载,静态代理程序运行,就加载字节码
为什么需要代理
被代理方法不能够满足功能需求,需要进行方法增强
案例05:Spring–05java动态代理
Spring AOP
AOP概述
AOP的概念
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
通俗而言
将程序中重复的代码抽取出来(例如一个方法),使其成为一个切面,在需要使用某个方法的时候,通过动态代理在不修改源码的基础上,利用该切面实现对该方法的增强。
AOP的作用
在程序运行期间,不修改源码就能对已有方法增强
AOP的优势
-
减少重复代码,对代码中重复部分进行抽取
-
提高开发效率,简化代码量
-
维护方便,修改方法的增强部分,不用修改已有方法
AOP的实现方式
利用动态代理技术
AOP的运用
分析案例04中存在的问题
部分代码
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}
......
}
-
业务层中没有事务控制,出现转账的业务时发生了异常,就会导致数据出错,违背了事务的一致性。
-
如果对每个事务都要加上事务开启和事务结束,会造成大量重复的代码,为了解决这个问题,需要用到动态代理技术,代码随用随加载。
解决事务没有被控制住和代码重复的问题
通过自定义事务控制器的方式来控制事务,通过动态代理来解决代码重复,并且进行事务控制。
案例06:Spring–06自定义事务控制器
Spring中的AOP
AOP相关术语
- Joinpoint(连接点),业务层中所有的方法
- Pointcut(切入点),业务层中需要增强的方法
- Advice(通知/增强),指拦截到 Joinpoint 之后所要做的事情就是通知。通知的类型有:前置通知,后置通知,异常通知,最终通知,环绕通知
- Introduction(引介),引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
- Target(目标对象),代理的目标对象
- Weaving(织入),是指把增强应用到目标对象来创建新的代理对象的过程。spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
- Proxy(代理),一个类被 AOP 织入增强后,就产生一个结果代理类
- Aspect(切面),是切入点和通知(引介)的结合
AOP开发步骤
-
编写核心业务代码。
-
抽取公共代码,制作成通知。
-
在配置文件中,声明切入点与通知间的关系,即切面。
基于XML的AOP配置
切入点表达式
关键字
execution(表达式)
表达式
访问修饰符 返回值 包名.包名.包名…类名.方法名(参数列表)
标准写法
void com.cncs.service.impl.AccountServiceImpl.save()
书写规则
- 返回值
- 返回值可以使用通配符,表示任意返回值
- eg:
void com.cncs.service.impl.AccountServiceImpl.save()
- eg:
- 返回值可以使用通配符,表示任意返回值
- 包名
- 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
- eg:
* *.*.*.*.AccountServiceImpl.save()
- eg:
- 包名可以使用…表示当前包及其子包
- eg:
* *..AccountServiceImpl.save()
- eg:
- 包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
- 类名和方法名
- 类名和方法名都可以使用*来实现通配
- eg:
* *..*.*()
- eg:
- 类名和方法名都可以使用*来实现通配
- 参数
- 直接写数据类型
- 基本类型
- 直接写名称
- eg:
int
- 引用类型
- 写包名.类名
- eg:
java.lang.String
- 基本类型
- 直接写数据类型
- 全通配写法
*..*.*(..)
- 实际开发常用写法
- 切到业务层实现类下的所有方法
- eg:
* com.cncs.service.impl.*.*(..)
aop相关标签
<aop:config>
- 作用:配置aop
<aop:aspect id="logAdvice" ref="logger">
- 作用:配置切面
- 属性:ref 引用通知bean的id
<aop:before method="printLog" pointcut="execution(* com.cncs.service.impl.*.*(..))"></aop:before>
- 作用:配置通知类型,并建立通知与切入点的关联
- 属性:
- pointcut 配置切入点表达式
- method 指定切入点方法
- 通知类型
<aop:before ...>
- 前置通知
<aop:after-returning ...>
- 后置通知
<aop:after-throwing ...>
- 异常通知
<aop:after ...>
- 最终通知
<aop:around ...>
- 环绕通知
案例07:Spring–07基于XML的AOP配置
基于注解的AOP配置
使用注解配置aop时,需要在bean中配置aop,开启对注解aop的支持
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
常用注解
-
切面
-
@Aspect 表明当前类是一个切面类
-
切入点
-
@Pointcut 配置切入点表达式
-
eg:
@Pointcut("execution(* com.cncs.service.impl.*.*(..))") private void pt1(){ }
-
-
通知
-
@Before
-
@AfterReturning
-
@AfterThrowing
-
@After
PS:使用以上四个通知,会在程序运行时出现通知顺序异常的问题
-
@Around
-
使用规则
- 需要有Object返回值
- 参数只能写:ProceedingJoinPoint pjp
-
eg:
@Around("pt1()") public Object aroundPrintLog(ProceedingJoinPoint pjp){ ...... }
PS:使用环绕通知不会出现通知顺序异常问题
-
-
案例08:Spring–08基于注解的AOP配置
Spring JdbcTemplate
概念
是对jdbc api的一层薄薄的封装,类似于appache的common-dbutils。
JdbcTemplate的使用
案例09:Spring–09JdbcTemplate的简单使用
三种数据源
PS:使用不同的数据源需要导入不同的坐标。
配置 C3P0 数据源
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_learn"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
配置 DBCP 数据源
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_learn"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
配置 spring 内置数据源
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/spring_learn"></property>
<property name="username" value="root"></property>
<property name="password" value="123456"></property>
</bean>
Spring中的事务控制
经典事务控制方式回顾
下面的事务控制方案还是原先的对jdbc操作,将事务提交,回滚,开启事务等操作进行aop配置实现事务控制。
基于XML的AOP配置实现事务控制
案例10:Spring–10基于XML的AOP配置实现事务控制
基于注解的AOP配置实现事务控制
案例11:Spring–11基于注解的AOP配置实现事务控制
Spring事务控制
Spring中进行事务控制相关API
PlatformTransactionManager接口
该接口有三个方法
TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
void commit(TransactionStatus status)
void rollback(TransactionStatus status)
该接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法,它有两个实现类
- DataSourceTransactionManager 使用 Spring JDBC 或 iBatis 进行持久化数据时
- HibernateTransactionManager 使用Hibernate 版本进行持久化数据时
TransactionDefinition
它是事务的定义信息对象,例如获取事务对象名称,事务隔离级别等等,其中比较重要的有:
-
事务传播行为 propagation
- SUPPORTS 支持事务,如果没有事务,就以非事务方式处理
- REQUIRED 默认,如果当前没有事务,就创建一个事务;如果有事务,就加入到这个事务来
-
事务超时时间 timeOut
- -1 默认,无超时限制
- 自定义(单位:秒)
-
事务是否可读 read-only
- true 查询用这种
- false 增删该用这种
TransactionStatus
此接口提供的是事务具体的运行状态
Spring进行事务管理的方式
-
编程式事务控制
-
声明式事务控制
-
基于XML的
-
基于注解的
-
Spring基于XML的声明式事务控制
案例12:Spring–12Spring基于XML的声明式事务控制
Spring基于注解的声明式事务控制
Spirng编程式事务控制(了解)
主要API
TransactionTemplate
通过在业务层对需要进行事务控制的方法进行编程处理,完成事务控制。
部分代码
只需在业务层编程处理,并在bean.xml中添加关于TransactionTemplate的配置,其余不做改变。
@Override
public void transfer(String sourceName, String targetName, float money) {
transactionTemplate.execute(new TransactionCallback<Object>() {
@Override
public Object doInTransaction(TransactionStatus transactionStatus) {
System.out.println("transfer...");
//2.根据名称查找source账户
Account sourceAccount = accountDao.findAccountByName(sourceName);
//3.根据名称查找target账户
Account targetAccount = accountDao.findAccountByName(targetName);
//4.source账户减少钱
sourceAccount.setMoney(sourceAccount.getMoney() - money);
//5.target账户增加钱
targetAccount.setMoney(targetAccount.getMoney() + money);
//更新source账户
accountDao.updateAccount(sourceAccount);
// int a = 1 / 0;
//更新target账户
accountDao.updateAccount(targetAccount);
return null;
}
});
学习地址:
参考博客: