以下笔记转载自github
IOC 和 DI 的概述
IOC(Inversion of Controll)
思想是反转资源获取的方向,传统的资源查找方式要求组件向容器发起请求查找资源。作为回应,容器适时的返回资源。而应用了IOC之后,则是容器主动的将资源推送给它所管理的组件,组件所要做的仅是选择一种合适的方式来接收资源
DI(Dependency Injection)
是IOC的另一种表述方式,即组件以一些预先定义好的方式(如:getter方法)来接收来自容器的资源注入
Spring配置
在SpringIOC容器读取bean配置创建bean实例之前,必须对它进行实例化。只有在容器实例化后,才可以从IOC容器里获取bean实例并使用
Spring提供了两种类型的IOC容器实现
- BeanFactory:IOC容器的基本实现,在调用getBean()方法时才会实例化对象
- ApplicationContext:提供了更多的高级特性,在加载配置文件后就会实例化对象。是BeanFactory的子接口
BeanFactory
是Spring框架的基础设施,面向Spring本身
ApplicationContext
面向使用Spring框架的开发者,几乎所有的应用场合都直接使用ApplicationContext
而非底层的BeanFactory
无论使用何种方式,配置文件时都是相同的
1 2 3 4 5
|
<bean id="people" class="learningspring.ioc.examples.People"> <property name="name" value="Chen"/> </bean>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| public class People { private String name;
public People() { }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
@Override public String toString() { return "People{" + "name='" + name + '\'' + '}'; } }
|
1 2 3 4 5 6 7 8 9 10
| @Test public void test(){ ApplicationContext ctx = new FileSystemXmlApplicationContext("F:\\Projects\\IdeaProjects\\LearningSpring\\src\\main\\java\\learningspring\\ioc\\examples\\applicationContext.xml");
People people = (People) ctx.getBean("people");
System.out.println(people); }
|
ApplicationContext
ApplicationContext
有两个实现类:
ClassPathXmlApplicationContext
:加载类路径里的配置文件
FileSystemXmlApplicationContext
:加载文件系统里的配置文件
Bean的相关配置
bean标签的id和name的配置
id
:使用了约束中的唯一约束。不能有特殊字符
name
:没有使用约束中的唯一约束(理论上可以重复,但是实际开发中不能出现)。可以有特殊字符
bean的生命周期的配置
init-method
:bean被初始化的时候执行的方法
destroy-method
:bean被销毁的时候执行的方法,前提是bean是单例的,工厂关闭
bean的作用范围的配置
scope
:bean的作用范围
- singleton:单例模式,默认的作用域。
- prototype:多例模式。
- request:应用在Web项目中,Spring创建这个类后,将这个类存入到request范围中。
- session:应用在Web项目中,Spring创建这个类后,将这个类存入到session范围中。
- globalsession:应用在Web项目中,必须在porlet环境下使用。但是如果没有这种环境,相当于session。
Spring的Bean管理配置
Spring的Bean的实例化方式
无参构造方式(默认)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public class Dog {
private String name; private Integer length;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getLength() { return length; }
public void setLength(Integer length) { this.length = length; }
@Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", length=" + length + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11
| <?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="dog" class="learningspring.ioc.examples.demo3.Dog"/>
</beans>
|
静态工厂实例化方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public class Car { private String name; private Double price;
public Car() { }
public Car(String name, Double price) { this.name = name; this.price = price; }
@Override public String toString() { return "Car{" + "name='" + name + '\'' + ", price=" + price + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13
| package learningspring.ioc.examples.demo3;
public class CarFactory {
public static Car createCar(){ return new Car(); } }
|
1 2 3 4 5 6 7 8 9 10
| <?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="car" class="learningspring.ioc.examples.demo3.CarFactory" factory-method="createCar"/>
</beans>
|
实例工厂实例化方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
public class Dog {
private String name; private Integer length;
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Integer getLength() { return length; }
public void setLength(Integer length) { this.length = length; }
@Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", length=" + length + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12
|
public class DogFactory {
public Dog createDog(){ return new Dog(); } }
|
1 2 3 4 5 6 7 8 9 10 11
| <?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="dogFactory" class="learningspring.ioc.examples.demo3.DogFactory"/> <bean id="dog2" factory-bean="dogFactory" factory-method="createDog"/> </beans>
|
Spring的属性注入方式
构造方法方式的属性注入
1 2 3 4 5 6 7 8 9 10
| <?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="car" class="learningspring.ioc.examples.demo3.Car"> <constructor-arg name="name" value="BWM"/> <constructor-arg name="price" value="800000"/> </bean> </beans>
|
Set方法方式的属性注入
1 2 3 4 5 6 7 8 9 10 11
| <?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="dog" class="learningspring.ioc.examples.demo3.Dog"> <property name="name" value="Golden"/> <property name="length" value="100"/> </bean> </beans>
|
为Bean注入引用类型的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?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="car" class="learningspring.ioc.examples.demo3.Car"> <constructor-arg name="name" value="BWM"/> <constructor-arg name="price" value="800000"/> </bean>
<bean id="dog" class="learningspring.ioc.examples.demo3.Dog"> <property name="name" value="Golden"/> <property name="length" value="100"/> </bean>
<bean id="employee" class="learningspring.ioc.examples.demo3.Employee"> <property name="name" value="Chen"/> <property name="car" ref="car"/> <property name="dog" ref="dog"/> </bean> </beans>
|
P名称空间的属性注入(Spring2.5)
- 通过引入p名称空间完成属性注入
- 普通属性:p:属性名=“值”
- 对象属性:p:属性名-ref=“值”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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="cat" class="learningspring.ioc.examples.demo3.Cat" p:name="Orange" p:length="100"/> <bean id="employee" class="learningspring.ioc.examples.demo3.Employee" p:cat-ref="cat"> <property name="name" value="Chen"/> <property name="car" ref="car"/> <property name="dog" ref="dog"/> </bean> </beans>
|
SpEL方式的属性注入(Spring3)
SpEL:Spring Expresssion Language 的表达式语言
语法:#{表达式}
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:p="http://www.springframework.org/schema/p" 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="cat2" class="learningspring.ioc.examples.demo4.Cat"> <property name="name" value="#{'Orange'}"/> <property name="length" value="#{101}"/> </bean> </beans>
|
注入集合类型的数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
public class CollectionBean {
private String[] strs; private List<String> list; private Set<String> set; private Map<String, String> map;
public void setStrs(String[] strs) { this.strs = strs; }
public void setList(List<String> list) { this.list = list; }
public void setSet(Set<String> set) { this.set = set; }
public void setMap(Map<String, String> map) { this.map = map; }
@Override public String toString() { return "CollectionBean{" + "strs=" + Arrays.toString(strs) + ", list=" + list + ", set=" + set + ", map=" + map + '}'; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
| <?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="collectionBean" class="learningspring.ioc.examples.demo4.CollectionBean"> <property name="strs"> <list> <value>Tom</value> <value>Jack</value> </list> </property>
<property name="list"> <list> <value>Lucy</value> <value>Lily</value> </list> </property>
<property name="set"> <set> <value>aaa</value> <value>bbb</value> <value>ccc</value> </set> </property>
<property name="map"> <map> <entry key="a" value="1"/> <entry key="b" value="2"/> </map> </property> </bean> </beans>
|
Spring分模块开发的配置
1
| ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext1.xml", "applicationContext2.xml");
|
1 2 3 4 5 6 7 8 9
| <?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">
<import resource="applicationContext2.xml"/> </beans>
|
Spring开发中的常用注解
@Component
该注解在类上使用,使用该注解就相当于在配置文件中配置了一个Bean,例如:
1 2 3 4 5 6 7
| @Component("userDao") public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("UserDaoImpl.save"); } }
|
相当于以下内容:
1
| <bean id="userDao" class="learningspring.ioc.examplesannotation.demo1.UserDaoImpl"></bean>
|
该注解有3个衍生注解:
- @Controller:修饰Web 层类
- @Service:修饰Service层类
- @Repository:修饰Dao层类
@Value
该注解用于给属性注入值,有2种用法
- 如果有set方法,则需要将该注解添加到set方法上
- 如果没有set方法,则需要将该注解添加到属性上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
|
@Component("dog") public class Dog { private String name;
@Value("100") private Double length;
public Dog() { }
public Dog(String name, Double length) { this.name = name; this.length = length; }
public String getName() { return name; }
@Value("Golden") public void setName(String name) { this.name = name; }
@Override public String toString() { return "Dog{" + "name='" + name + '\'' + ", length=" + length + '}'; } }
|
@Autowired
@Value
通常用于普通属性的注入。
@Autowired
通常用于为对象类型的属性注入值,但是按照类型完成属性注入
习惯是按照名称完成属性注入,所以必须让@Autowired
注解和@Qualifier
注解一同使用。
(如果没有@Qualifier
注解,修改以下例子中@Repository
注解的值,也能编译成功)
1 2 3 4 5 6 7 8 9 10 11 12 13
| @Service("userService") public class UserServiceImpl implements UserService {
@Autowired @Qualifier("userDao") private UserDao userDao;
@Override public void save() { System.out.println("UserServiceImpl.save"); userDao.save(); } }
|
1 2 3 4 5 6 7
| @Repository("userDao") public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("UserDaoImpl.save"); } }
|
@Resource
该注解也可以用于属性注入,通常情况下使用**@Resource注解,默认按照名称**完成属性注入。
该注解由J2EE提供,需要导入包javax.annotation.Resource
。
@Resource
有两个重要的属性:name
和type
,而Spring将@Resource
注解的name
属性解析为bean的名字,而type
属性则解析为bean的类型。所以,如果使用name
属性,则使用byName的自动注入策略,而使用type
属性时则使用byType自动注入策略。如果既不制定name
也不制定type
属性,这时将通过反射机制使用byName自动注入策略。
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@Controller("userController") public class UserController { @Resource(name = "userService") private UserService userService; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
@Service("userService") public class UserServiceImpl implements UserService {
@Resource(name = "userDao") private UserDao userDao;
@Override public void save() { System.out.println("UserServiceImpl.save"); userDao.save(); } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
@Component("userDao") public class UserDaoImpl implements UserDao { @Override public void save() { System.out.println("UserDaoImpl.save"); } }
|
@PostConstruct 和 @PreDestroy
@PostConstruct
和@PreDestroy
注解,前者为Bean生命周期相关的注解,在配置文件中,使用到了init-method
参数来配置Bean初始化之前要执行的方法,该注解也是同样的作用。将该注解添加到想在初始化之前执行的目标方法上,就可以实现该功能,而后者则是Bean生命周期中destroy-method
目标方法,使用该注解在指定方法上,即可实现在Bean销毁之前执行该方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Component("userDao") public class UserDaoImpl implements UserDao { @PostConstruct public void init(){ System.out.println("UserDaoImpl.init"); } @Override public void save() { System.out.println("UserDaoImpl.save"); } @PreDestroy public void destroy(){ System.out.println("UserDaoImpl.destroy"); } }
|
@Scope
Bean的作用范围的注解,默认为singleton(单例)
可选值:
- singleton
- prototype
- request
- session
- globalsession
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
@Component("userDao") @Scope public class UserDaoImpl implements UserDao {
@PostConstruct public void init(){ System.out.println("UserDaoImpl.init"); }
@Override public void save() { System.out.println("UserDaoImpl.save"); }
@PreDestroy public void destroy(){ System.out.println("UserDaoImpl.destroy"); } }
|
基于XML配置和基于注解配置的对比
|
基于XML的配置 |
基于注解的配置 |
Bean的定义 |
<bean id=”Bean的id” class=”类的全路径”/> |
@Component或衍生注解@Controller,@Service和@Repository |
Bean的名称 |
通过id或name指定 |
@Component(“Bean的id”) |
Bean的属性注入 |
<property>或者通过p命名空间 |
通过注解@Autowired 按类型注入 通过@Qualifier按名称注入 |
Bean的生命周期 |
init-method指定Bean初始化前执行的方法,destroy-method指定Bean销毁前执行的方法 |
@PostConstruct 对应于int-method @PreDestroy 对应于destroy-method |
Bean的作用域 |
在bean标签中配置scope属性 |
@Scope, 默认是singleton 配置多例可以在目标类上使用@Scope(“prototype”) |
使用场景 |
Bean来自第三方,可以使用在任何场景 |
Bean的实现类由自己维护 |
XML可以适用于任何场景,就算Bean来自第三方也可以适用XML方式来管理。而注解方式就无法在此场景下使用,注解方式可以让开发的过程更加方便,但前提是Bean由自己维护,这样才能在源码中添加注解。
所以可以使用两者混合的方式来开发项目,使用XML配置文件来管理Bean,使用注解来进行属性注入
Spring AOP
AOP的概述
即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP切入点表达式语法
AOP切入点表达式是基于execution的函数完成的
语法:**[访问修饰符] 方法返回值 包名.类名.方法名(参数)**
“*” 表示任意返回值类型
“..” 表示任意参数
public void learningspring.aop.aspectj.xml.demo2.ProductDaoImpl.save(..)
:具体到某个增强的方法
* *.*.*.*Dao.save(..)
:所有包下的所有以Dao结尾的类中的save方法都会被增强
* learningspring.aop.aspectj.xml.demo2.ProductDaoImpl+.save(..)
:ProductDaoImpl及其子类的save方法都会被增强
* learningspring.aop.aspectj.xml..*.*(..)
:xml包及其子包的所有类的方法都会被增强
AspectJ的注解配置案例
首先也是创建一个接口ProductDao
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
public interface ProductDao {
void save();
void query();
void modify();
String delete(); }
|
然后创建一个Dao实现类ProductDaoImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
public class ProductDaoImpl implements ProductDao {
@Override public void save() { System.out.println("添加商品"); }
@Override public String delete() { System.out.println("删除商品"); return new Date().toString(); }
@Override public void modify() { System.out.println("修改商品"); }
@Override public void query() { System.out.println("查询商品"); int a = 1/0; } }
|
接着创建增强类ProductEnhancer
,在该类里面使用注解
使用@Pointcut
注解可以配置切入点信息,在较多的方法都要使用同一个增强时,就可以配置一个切入点让目标方法都去引用
@Before
:前置增强
@AfterReturning
:后置增强,其中的returning的值必须和方法传入的参数名相同
@Around
:环绕增强
@AfterThrowing
:异常抛出增强,其中的throwing的值必须和方法传入的参数名相同
@After
:最终增强
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
|
@Aspect public class ProductEnhancer {
@Pointcut(value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.*(..))") private void pointcut1(){}
@Before(value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.save())") public void checkPri(JoinPoint joinPoint){ System.out.println("【前置增强】权限校验" + joinPoint); }
@AfterReturning(returning = "result", value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.delete())") public void writeLog(Object result){ System.out.println("【后置增强】写入日志 操作时间:" + result.toString()); }
@Around(value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.modify())") public Object monitor(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("【环绕增强】当前空闲内存" + Runtime.getRuntime().freeMemory()/(1024 * 1024) + "MB"); Object obj = joinPoint.proceed(); System.out.println("【环绕增强】当前空闲内存" + Runtime.getRuntime().freeMemory()/(1024 * 1024) + "MB"); return obj; }
@AfterThrowing(throwing = "ex", value = "execution(* learningspring.aop.aspectj.annotation.demo2.ProductDaoImpl.query())") public void exception(Throwable ex){ System.out.println("【异常抛出增强】" + "异常信息:" +ex.getMessage()); }
@After(value = "pointcut1()") public void finallyAdvice(){ System.out.println("【最终增强】" + new Date().toString()); } }
|
编写测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
|
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:aspectj-annotation.xml") public class AppTest {
@Resource(name = "productDao") private ProductDao productDao;
@Test public void test(){
productDao.save();
productDao.delete();
productDao.modify();
productDao.query(); } }
|
运行,控制台输出
1 2 3 4 5 6 7 8 9 10 11 12 13
| 【前置增强】权限校验execution(void learningspring.aop.aspectj.annotation.demo2.ProductDao.save()) 添加商品 【最终增强】Tue Mar 19 16:01:06 CST 2019 删除商品 【最终增强】Tue Mar 19 16:01:06 CST 2019 【后置增强】写入日志 操作时间:Tue Mar 19 16:01:06 CST 2019 【环绕增强】当前空闲内存186MB 修改商品 【环绕增强】当前空闲内存186MB 【最终增强】Tue Mar 19 16:01:06 CST 2019 查询商品 【最终增强】Tue Mar 19 16:01:06 CST 2019 【异常抛出增强】异常信息:/ by zero
|
Spring事务管理
什么是事务
事务:逻辑上的一组操作,组成这组操作的各个单元,要么全部成功,要么全部失败。
事务的特性
- 原子性:事务不可分割
- 一致性:事务执行前后数据完整性保持一致
- 隔离性:一个事务的执行不应该受到其他事务的干扰
- 持久性:一旦事务结束,数据就持久化到数据库
不考虑隔离性引发的安全性问题
- 读问题
- 脏读:A事务读到B事务未提交的数据
- 不可重复读:B事务在A事务两次读取数据之间,修改了数据,导致A事务两次读取结果不一致
- 幻读/虚读:B事务在A事务批量修改数据时,插入了一条新的数据,导致数据库中仍有一条数据未被修改。
- 写问题
解决读问题
- 设置事务的隔离级别
Read uncommitted
:未提交读,任何读问题都解决不了
Read committed
:已提交读,解决脏读,但是不可重复读和幻读有可能发生
Repeatable read
:重复读,解决脏读和不可重复读,但是幻读有可能发生
Serializable
:解决所有读问题,因为禁止并行执行
Spring事务管理API
PlatformTransactionManager
:平台事务管理器
DataSourceTransactionManager
:底层使用JDBC管理事务
TransactionDefinition
:事务定义信息
用于定义事务相关的信息,隔离级别,超时信息,传播行为,是否只读……
TransactionStatus
:事务的状态
用于记录在事务管理过程中,事务的状态
API的关系:
Spring在进行事务管理的时候,首先平台事务管理器根据事务定义信息进行事务的管理,在事务管理过程中,产生各种状态,将这些状态信息记录到事务状态对象
Spring事务的传播行为
首先假设一个背景,Service1里的x()方法已经定义了一个事务,Service2里的y()方法也有一个事务,但现在新增一行代码在Service2的y()方法中要先调用Service1里的x()方法然后再执行本身的方法。这时就涉及到事务的传播行为。
Spring中提供了7种传播行为
假设x()方法称为A,y()方法称为B
- 保证多个操作在同一个事务中
PROPAGATION_REQUIRED
(*):Spring事务隔离级别的默认值。如果A中有事务,则使用A中的事务。如果没有,则创建一个新的事务,将操作包含进来。
PROPAGATION_SUPPORTS
:支持事务。如果A中有事务,使用A中的事务。如果A没有事务,则不使用事务。
PROPAGATION_MANDATORY
:如果A中有事务,使用A中的事务。如果没有事务,则抛出异常。
- 保证多个事务不在同一个事务中
PROPAGATION_REQUIRES_NEW
(*):如果A中有事务,将A的事务挂起,创建新事务,只包含自身操作。如果A中没有事务,创建一个新事物,包含自身操作。
PROPAGATION_NOT_SUPPORTED
:如果A中有事务,将A的事务挂起,不使用事务。
PROPAGATION_NEVER
:如果A中有事务,则抛出异常。
- 嵌套式事务
PROPAGATION_NESTED
(*):嵌套事务,如果A中有事务,则按照A的事务执行,执行完成后,设置一个保存点,再执行B中的操作,如果无异常,则执行通过,如果有异常,则可以选择回滚到初始位置或者保存点。
Spring事务管理案例——转账情景
转账情景实现
首先创建接口AccountDao
,定义两个方法分别是out
和in
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
|
public interface AccountDao {
void out(String from, double money);
void in(String to, double money); }
|
接着创建实现类AccountDaoImpl
实现out
和in
方法并且继承JdbcSupport
类。这样就可以直接使用父类的JDBCTemplate
对象。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override public void out(String from, double money) { this.getJdbcTemplate().update("UPDATE account SET money = money - ? WHERE name = ?", money, from); }
@Override public void in(String to, double money) { this.getJdbcTemplate().update("UPDATE account SET money = money + ? WHERE name = ?", money, to); } }
|
然后创建接口AccountrService
,定义transfer
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public interface AccountService {
void transfer(String from, String to, Double money); }
|
再创建类AccountServiceImpl
实现该接口,并声明AccountDao
引用并创建set
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
@Override public void transfer(String from, String to, Double money) { accountDao.out(from, money); accountDao.in(to, money); } }
|
最后创建配置文件spring-tx-programmatic.xml
,用来管理Bean。
引入数据库连接文件,配置数据源,创建Bean对象accountDao
将数据源dataSource
注入到accountDao
中,再创建Bean对象accountService
,将accountDao
注入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 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="learningspring.transaction.programmatic.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean>
<bean id="accountDao" class="learningspring.transaction.programmatic.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean>
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
|
到此一个转账模拟业务就实现了,数据库表依然使用前面创建的account
表,先查询当前数据库的数据。
编写测试方法,实现让姓名为Bob的账户向Jack转账1000元。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(value = "classpath:spring-tx-programmatic.xml") public class AppTest {
@Resource(name = "accountService") private AccountService accountService;
@Test public void test(){ accountService.transfer("Bob","Jack",1000d); } }
|
运行结果
现在对类AccountServiceImpl
里的transfer
方法进行修改,让其发生异常,再观察结果
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
@Override public void transfer(String from, String to, Double money) { accountDao.out(from, money); int i = 1/0; accountDao.in(to, money); }
}
|
查询数据库数据
这时Bob账户的钱就少了1000元,而Jack账户也没有增加1000元。
所以就需要事务来进行管理。
编程式事务
所谓编程式事务,就是要在源码中编写事务相关的代码。实现编程式事务,首先要在AccountServiceImpl
中声明TransactionTemplate
对象,并创建set方法。然后修改transfer
参数列表所有参数都用final
(因为使用了匿名内部类)修饰,并修改方法体内容。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
|
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
private TransactionTemplate transactionTemplate;
public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; } public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; }
@Override public void transfer(final String from, final String to, final Double money) { transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { accountDao.out(from, money); int i = 1/0; accountDao.in(to,money); } }); } }
|
然后修改spring-tx-programmatic.xml
文件,创建Bean对象transactionManager
和transactionTemplate
,并将transactionTemplate
注入到accountService
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 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="learningspring.transaction.programmatic.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> <property name="transactionTemplate" ref="transactionTemplate"/> </bean>
<bean id="accountDao" class="learningspring.transaction.programmatic.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean>
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="transactionManager"/> </bean> </beans>
|
此时异常依然存在,数据库数据仍然是上次执行的结果状态
再次运行测试方法,并查询结果,观察是否发生变化
现在就实现了编程式事务,当出现异常时,数据库的数据就不会被修改。
声明式事务
XML配置方式
声明式事务即通过配置文件实现,利用的就是Spring的AOP
修改类AccountServiceImpl
,删除TransactionTemplate
对象,并修改transfer
方法,保留异常代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
@Override public void transfer(String from, String to, Double money) { accountDao.out(from, money); int i = 1/0; accountDao.in(to,money);
} }
|
然后创建配置文件spring-tx-declarative.xml
,配置数据源即Bean对象,然后配置事务管理器。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 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="learningspring.transaction.declarative.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean>
<bean id="accountDao" class="learningspring.transaction.declarative.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean>
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
|
接着就配置事务的增强,配置文件中加入以下配置
1 2 3 4 5 6 7 8 9 10 11 12 13
| <tx:advice id="txAdvice" transaction-manager="transactionManager"> <tx:attributes> <tx:method name="*" propagation="REQUIRED"/> </tx:attributes> </tx:advice>
<aop:config> <aop:pointcut id="pointcut1" expression="execution(* learningspring.transaction.declarative.AccountServiceImpl.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="pointcut1"/> </aop:config>
|
先查看当前数据库数据
编写测试方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(value = "classpath:spring-tx-declarative.xml") public class AppTest {
@Resource(name = "accountService") private AccountService accountService;
@Test public void test(){ accountService.transfer("Bob","Jack",1000d); } }
|
运行查看结果,是否变化
至此就实现了声明式事务XML方式的配置。
注解配置方式
Spring的事务配置仍然支持注解配置
继续沿用spring-tx-declarative.xml
文件,把事务增强和AOP相关的配置注释,并开启注解事务。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd 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="learningspring.transaction.declarative.AccountServiceImpl"> <property name="accountDao" ref="accountDao"/> </bean>
<bean id="accountDao" class="learningspring.transaction.declarative.AccountDaoImpl"> <property name="dataSource" ref="dataSource"/> </bean>
<context:property-placeholder location="db.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="${jdbc.driverClassName}"/> <property name="jdbcUrl" value="${jdbc.url}"/> <property name="user" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean>
<tx:annotation-driven transaction-manager="transactionManager"/> </beans>
|
接下来就可以在业务层类上使用事务管理的注解了。修改AccountServiceImpl
类,添加@Transactional
注解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
|
@Transactional(rollbackFor = Exception.class) public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) { this.accountDao = accountDao; }
@Override public void transfer(String from, String to, Double money) { accountDao.out(from, money); int i = 1/0; accountDao.in(to,money);
} }
|
再次运行测试方法,数据库也不会发生改变。