1. 初识spring:
Spring下载地址:www.springsource.org/download
(1) 使用spring的必要jar文件:
dist\spring.jar
lib\jakarta-commons\commons-logging.jar
(2) 如果使用了切面编程(AOP),还需要下列jar文件:
lib\aspectj\aspectjweaver.jar
lib\aspectj\aspectjrt.jar
lib\cglib\cglib-nodep-2.1_3.jar
(3) 如果使用了spring2.5中的注解,如@Resource/@PostConstruct/@PreDestroy, 还需要下列jar文件:
lib\j2ee\common-annotations.jar
(4) 如果搭建spring+JDBC环境,还需要下列jar文件:
/lib/jakarta-commons/common-dbcp.jar
/lib/jakarta-commons/common-pool.jar
/lib/jakarta-commons/commons-collections.jar
/lib/jakarta-commons/commons-logging.jar(上面已有)
2. 定义一个<bean>时候既提供了id也提供了name为什么呢:
<bean id=”” name="">
</bean>
Id是默认xml中确定元素唯一性的属性,但是id中不能出现特殊字符或符号,为了支持特殊字符和符号提供了name属性,但是一般情况下如果没有特殊字符和符号,最好还是使用id。
3. Bean的作用域:
ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml");
PersonService personService1
=(PersonService)ctx.getBean("personService");
PersonService personService2
=(PersonService)ctx.getBean("personService");
以上方式获取的两个对象personService1和personService2是同一个实例呢?还是不同的实例呢?-----------------------------------------------------------------------答案:是同一个对象。
3.1 Bean的作用域有五种:
3.1.1 Singleton(单例):
每次从容器获取的bean都是同一个对象,即都是同一个实例。如:
<bean name="personService" class=”cn.itcast.OrderServiceBean" scope="singleton">
3.1.2 Prototype:
每次从容器获取的bean都是新的对象,即都不是同样的实例。如:
<bean name="personService" class=”cn.itcast.OrderServiceBean" scope="prototype">
3.1.3 Request:应用于web中的request范围;
3.1.4 Session:应用于web中的session范围;
3.1.5 Global session:作为全局作用域用于session范围。
4. Bean的生命周期管理:
4.1 容器对bean初始化的时间:
默认情况下,也就是当bean的作用域(scope的值)是singleton时候,容器实例化的时候自动对bean进行实例化。
当bean的作用域(scope的值)是prototype时候,只有在ctx.getBean(“personService”);时候才会进行实例化。
4.2 修改bean初始化的的时间(如果想使用单例作用域又不想让bean在容器启动时候初始化怎么做?):
在每个spring Ioc容器中的一个bean定义只有一个对象实例。默认情况下会在容器启动时候初始化bean,但我们可以指定Bean节点的lazy-init=”true” 来延迟初始化bean,这时候,只有第一次获取的bean才会初始化bean,如:
<bean id=”xxx” class=”cn.itcast.OrderServiceBean” lazy-init=”true”/>
如果想对所有bean都应用延迟初始化,可在根节点bean设置default-lazy-init=”true”, 如下:<beans default-lazy-init=”true”…>
注意:这个属性开发时候最好还是不要用,因为有错误时只有当ctx.getBean()时候才会出现,而开发时候一般是希望有错误启动就能发现错误。
4.3 如果某个bean的作用是对数据库的链接进行管理,则要求bean初始化后自动进行数据库相关链接操作,当容器关闭之前又会自动数据库链接操作。如
PersonServiceBean.java所定义:
public class PersonServiceBean implements PersonService
{
public PersonServiceBean()
{
System.out.println("我被实例化了");
}
public void init()
{
System.out.println("打开数据库相关链接操作");
}
public void save()
{
System.out.println("我是save()方法");
}
public void destroy()
{
System.out.println("关闭数据库相关操作");
}
}
修改配置文件对此bean的定义:
<bean name="personService" class="cn.itcast.service.impl.PersonServiceBean" init-method="init" destroy-method="destroy">
</bean>
其中init-method=”init”表示初始化此bean后自动执行PersonServiceBean.java中定义的init()方法,destroy-method=”destroy”表示当容器关闭前自动执行PersonServiceBean.java中定义的destroy()方法。
注意:默认一个bean被创建,容器默认不会将其关闭,直到容器被关闭,不过这里的容器关闭指的是通过程序方式将容器关闭而不是手动关闭,程序方式关闭容器如下:
AbstractApplicationContext aac=new
ClassPathXmlApplicationContext("beans.xml");
aac.close();
拥有关闭容器的类是AbstractApplicationContext这个抽象类,所以必须把生成的容器实例赋值给它。
5. 依赖注入(对象属性和基本类型的属性注入,这里对象注入采用的是属性注入方式)
容器注入所做工作:本来在PersonServiceBean中定义了一个PersonDaoBean对性属性,容器的工作就是将PersonDaoBean实例对象注入到PersonServiceBean中。
(1)提供要注入的bean类:
public class PersonServiceBean implements PersonService
{
private PersonDaoBean personDaoBean;
//以前我们定义对象属性时候是在创建类的时候就直接实例化,如:
// private PersonDaoBean personDaoBean=new PersonDaoBean();
//现在我们将对象属性的注入直接通过setter方法教给容器注入
private String personName;
//这里基本类型属性也是通过容器注入的,甚至可以在注入的时候赋初值
public String getPersonName()
{
return personName;
}
//省略personDaoBean和personName的getter()、setter()方法
public void save()
{
System.out.println("user's name is"+personName);
personDaoBean.add();
}
}
注意:在PersonServiceBean.java中并没有对personDaoBean进行实例化,而是通过set()方法利用容器将外部PersonDaoBean注入到PersonServiceBean中。
(2)修改<bean>文件:
· 方法一(引用外部bean):
<bean id="personDaoBean" class="cn.itcast.dao.impl.PersonDaoBean">
</bean>
<bean name="personService" class="cn.itcast.service.impl.PersonServiceBean">
<property name="personDaoBean" ref="personDaoBean"/>
<property name="personName" value="zhangsan"/>
</bean>
首先,定义PersonDaoBean这个bean,然后定义PersonServiceBean这个bean。
<property name="personDaoBean" ref="personDaoBean"/>中的name的值是指在PersonServiceBean.java中定义的personDaoBean属性(更准确的说法是setPersonDaobean()这个方法名字中的personDaoBean),ref的值是指上面定义的PersonDaoBean这个bean。
· 方法二(设置内部bean):
<bean name="personService" class="cn.itcast.service.impl.PersonServiceBean">
<property name="personDaoBean" >
<bean class="cn.itcast.dao.impl.PersonDaoBean"></bean>
</property>
<property name="personName" value="李四"/>
</bean>
注意:这种方式有一定局限性,这里定义的<bean class="cn.itcast.dao.impl.PersonDaoBean"/>不能为别的bean使用,它被定义为由personServiceBean似有的了。
6. 各种集合类型属性注入:
各种集合类型主要包括:Set、List、Properties、Map。
(1) 在类中定义各种集合属性:
public class PersonServiceBean implements PersonService
{
private PersonDaoBean personDaoBean;
private String personName;
private Set<String> sets=new HashSet<String>();
private List<String> lists=new ArrayList<String>();
private Properties props=new Properties();
private Map<String, String> maps=new HashMap<String, String>();
//各个属性的getter()和setter()方法
}
(2)修改bean.xml配置文件,将各种集合类型属性注入bean中:
<bean id="personDaoBean" class="cn.itcast.dao.impl.PersonDaoBean"></bean>
<bean name="personService" class="cn.itcast.service.impl.PersonServiceBean">
<property name="personDaoBean" ref="personDaoBean"/>
<property name="personName" value="zhangsan"/>
<property name="sets">
<set>
<value>FirstSet</value>
<value>SecondSet</value>
</set>
</property>
<property name="lists">
<list>
<value>FirstList</value>
<value>SecondList</value>
</list>
</property>
<property name="props">
<props>
<prop key="propkey1">FirstProp</prop>
<prop key="propkey2">SecondProp</prop>
</props>
</property>
<property name="maps">
<map>
<entry key="mapkey1" value="FirstMap1"/>
<entry key="mapkey2" value="SecondMap2"/>
</map>
</property>
</bean>
7. 使用构造器将属性值注入bean
在上面所有的注入,都是采用的属性setter方法将属性值注入到bean里的,我们也可以用构造器将属性值注入到bean中。
(1) 必须在具体要注入的bean中定义好构造方法:
public class PersonServiceBean implements PersonService
{
private PersonDaoBean personDaoBean;
private String personName;
public PersonServiceBean(PersonDaoBean personDaoBean,String personName)
{
this.personDaoBean=personDaoBean;
this.personName=personName;
}
//省略personDaoBean和PersonName的getter()、setter()方法 }
(2) 修改beans.xml配置文件,添加构造器:
<bean id="personDaoBean" class="cn.itcast.dao.impl.PersonDaoBean"></bean>
<bean name="personService" class="cn.itcast.service.impl.PersonServiceBean">
<constructor-arg index="0" type="cn.itcast.dao.impl.PersonDaoBean" ref="personDaoBean"/>
<constructor-arg index="1" type="java.lang.String" value="zhangsan"/>
注解:index的值表示构造函数的第几个参数(从零开始计数),type的值表示构造函数的参数属于的类型(如果是自定义类则要写全包名和类型,默认type是String类型),ref和value都表示需要注入的值(如果参数是自定义类,则采用ref,其值为之前定义的bean的id;如果参数是java基本类型,则直接填入具体值)。
8. 用@Resource注解完成属性装配
在上面的7和8中对象注入分别采用的属性注入方式和构造器注入方式,这两种注入都有明显的缺点:操作复杂繁多,需要在beans.xml中配置大量信息。现在提供第三种对象属性注入方式,叫注解注入方式。
在java代码中使用@Autowired或@Resource注解方式进行注入,这两个注入方式的区别是@Autowired默认按类型装配(即:根据类中定义的对象的类型名到容器中寻找对应的类),@Resource默认按名称装配(即:根据类中定义的对象属性的名称到容器中寻找和对象名称一样名字的类名),如果没有指定name属性,并且按照默认的名称仍然找不到依赖对象时候,@Resource注解会会退到按类型注入,但是一旦指定了name属性就只能按名称注入了。
@Autowired是spring容器提供的,@Resource是J2EE内部提供的而且在JDK里也已经内部提供,功能比前者稍强,而且使用它还能减少与spring框架之间的耦合,所以推荐使用它。
(1) 在使用注解方式注入依赖对象属性前,必须修改beans.xml中的命名空间(主要是添加命名相关命名空间使得支持注解注入功能,背景色为黄色的内容),然后打开注解方式注入依赖对象属性的支持(背景色为紫色的内容):
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config/>
(2) 必须给需要注入功能的类提供无参构造方法
(3) @Resource注解注入语法:
方法一:在定义对象属性对象之前加上注解:
@Resource
private PersonDaoBean personDaoBean;
或写全指定要注解的对象属性的名称:
@Resource(name="personDaoBean")
private PersonDaoBean personDaoBean;
方法二:在对象属性的setter()方法之前加上注解:
@Resource
public void setPersonDaoBean(PersonDaoBean personDaoBean)
{
this.personDaoBean = personDaoBean;
}
(4) @AutoWired注解注入语法:
@Autowired
private PersonDaoBean personDaoBean;
注意@AutoWired注解默认是使用类名匹配方式注入,但也可以修改为按名称匹配方式注入:
@Autowired @Qualifier("personDaoBean")
private PersonDaoBean personDaoBean;
9. 自动注入依赖对象(简略了解就行,不必深究!)
注入依赖对象可以使用手工注入或自动注入方式,在实际应用中建议使用手工注入,因为自动注入会产生未知情况,开发人员无法预见最终的注入结果。例子:
<bean id=”…” class=”…” autowire=”byType” >
Autowire属性取值如下:
(1) byType:按类型注入,可以根据属性的类型,在容器中寻找跟该类型匹配的bean。如果发现独个匹配的bean,那么会抛出异常,如果没有找到,即属性值为null。
(2) byname:按名称装配,可以根据属性的名称,在容器中寻找跟该属性名相同的bean,如果没有找到,即属性值为null。
(3) constructor:与byType的方式类似,不同之处在于他应用于构造器参数。如果在容器中没有找到与构造器参数一致的bean,那么将会抛出异常。
(4) autodetect:通过bean类的自省机制(introspection)来决定是使用constructor还是byType方式进行自动注入。如果发现默认的构造器,那么将使用byType方式。
10. 自动扫描和管理Bean
在spring2.5版本之前所有的bean都是需要像上面所描述的方式一个一个的配置,项目中bean肯定会有非常非常多的bean,配置起来相当麻烦。在spring2.5版本开始拥有了一项新功能,能让开发人员不需在beans.xml中配置大量bean,此项功能能让spring容器自动扫描和管理bean。此项技术使用的是注解方式,它可以在类路径底下寻找标注了@Component、@Service、@Controller、@Repository注解的类,并把这些类纳入进spring容器中进行管理,它的作用和在xml文件中使用bean节点配置组件是一样的。
10.1 对各个注解的解释:
(1) @Service:用于标注业务层组件;
(2) @Controller:用于标注控制层组件(如:struts中的action);
(3) @Repository:用于标注数据库访问组件,即DAO组件;
(4) @Component:泛指组件,当组件不好归类的时候,我们可以使用这个注解进行标注.
10.2 实现步骤:
(1) 首先,修改beans.xml配置文件的命名空间使得支持这项功能(其实就是跟上面配置注解方式注入依赖属性一样的操作,然后打开支持自动扫描和管理bean的支持(背景色为紫色的内容):
xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:component-scan base-package="cn.itcast"/>
注意:
1. base-package的值表示需要交给容器管理的类所在的包,并且,如果此包为“cn.itcast”则此包以及其子包下的类都能被配置成bean。
2. 在这里省略了<context:annotation-config/>配置的打开,因为在<context:component-scan base-package="cn.itcast"/>里包括了它的定义。
(2) 用注解方式告诉容器那些类需要交给它来管理:
@Service
/*
*将PersonServiceBean作为服务层bean交给容器管理
*在这里默认如果没有指定别名,则spring默认将personServiceBean(类名首字
*母小写)作为bean的id,如果想要改别名,语法为:@Service(“personService”)
*/
@Scope("prototype")//改变默认的singleton类型为prototype类型
public class PersonServiceBean implements PersonService
{
private PersonDaoBean personDaoBean;
private String personName;
public PersonServiceBean()
{}
public PersonServiceBean(PersonDaoBean personDaoBean,String personName)
{
this.personDaoBean=personDaoBean;
this.personName=personName;
}
@PostConstruct//设置此方法在初始化一旦完成后就立即执行
public void init()
{
System.out.println("我被初始化了");
}
public void save()
{
personDaoBean.add();
}
@PreDestroy//设置此方法在spring容器关闭前自动执行
public void destroy()
{
System.out.println("关闭资源");
}
}
@Repository//将PersonDaoBean作为Repository层bean交给容器管理
public class PersonDaoBean implements PersonDao
{
public void add()
{
System.out.println("执行PersonDao类中add()方法");
}
}
3. 对AOP相关概念了解:
(1)Aspect(切面):指横切性关注点的抽象即为切面,它与雷相似,只是两者的关注点不一样,类是对物体特征的抽象,而切面是横切性关注点的抽象。
(2)Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点,实际上joinpoint还可以是field或构造器。
(3)Pointcut(切入点):所谓切入点是指我们要对那些joinpoint进行拦截的定义。
(4)Advice(通知):所谓通知时拦截到joinpoint之后所要做的事情就是通知,通知分为牵制通知,后置通知,异常通知,最终通知和环绕通知。
(5)Target(目标对象):代理的目标对象。
(6)Weave(织入):值将aspects应用到target对象并导致proxy对象创建的过程称为织入。
(7)Introduction(引入):在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法或field。
4. Spring实现AOP
Spring提供了两种切面使用方式,实际工作中我们可以选其中一种就行。
· 基于注解方式进行AOP开发
· 基于XML配置方式进行AOP开发
开发前提:修改beans.xml使得支持AOP开发:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="ttp://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-2.5.xsd"
<context:component-scan base-package="cn.itcast"/>
<aop:aspectj-autoproxy/>
</beans>
a) 基于注解方式进行AOP开发:
(1)首先,打开AOP开发支持:
<aop:aspectj-autoproxy/>
然后,定义服务层接口以及实现服务层接口:
package cn.itcast.service.impl;
import org.springframework.stereotype.Service;
import cn.itcast.service.PersonService;
@Service("personService")
public class PersonServiceBean implements PersonService
{
@Override
public String getPersonName(Integer id)
{
System.out.println("我是getPersonName()方法");
return "xxx";
}
@Override
public void save(String name)
{
// System.out.println(2/0);
//这是用于测试上面的例外通知的
System.out.println("我是save()方法");
}
@Override
public void update(String name, Integer id)
{
System.out.println("我是update()方法");
}
}
(2)然后,建一个切面的代理,这是一个类,名叫MyInterceptor,名称由自己定义:
@Aspect
@Component//如果我们使用注解方式注解bean就使用@Component
public class MyInterceptor
{
@Pointcut("execution(* cn.itcast.service.impl.PersonServiceBean.* (..))")
//这里的注解表达式解释详情请看下面
private void anyMethod()
{}
@Before("anyMethod()")
//括号里填写切入点,在spring中即方法,anyMethod()表示所有任何方法,
即可以拦截到所有方法,下面注解括号中也是同样作用
private void doAccessCheck()
{
System.out.println("前置通知");
}
@AfterReturning("anyMethod()")
public void doAfterReturning()
{
System.out.println("后置通知");
}
@After("anyMethod()")
public void doAfter()
{
System.out.println("最终通知");
}
@AfterThrowing("anyMethod()")
public void doAfterThrowing()
{
System.out.println("例外通知");
}
@Around("anyMethod()")
//这是环绕通知,struts2里面提供的拦截器就是环绕通知,
//环绕通知在解决权限问题时候被大量使用。
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable
{
Object result = null;
// if ()判断用户是否拥有权限
// {
System.out.println("进入方法");
result = pjp.proceed();
System.out.println("退出方法");
// }
return result;
}
}
对注解表达式注解:
① 返回值的详解:
返回值类型为“*”时候表示任意类型;
返回值类型为“java.lang.String”表示String类型。注意:String不能写成“String”;
返回值类型为“!void”,即只拦截又返回值的方法,无返回值的方法统统都不拦截,
如:execution(!void cn.itcast.service..*.*(..))
② 对方法参数的详解:
当要拦截的方法中的参数至少为一个并且第一个参数为java.lang.String时候,
参数表达式可写成:(java.lang.String,..)
③ 对拦截的包的详解:
Execution(* cn.itcast.service..*.*(..))中 cn.itcast.service是一个包名,包名后加了“..”表示这个service下的所有子包,随后的“*”表示前面定义包的所有类,在随后的“*”表示前面定义的类的所有方法。
Execution(* cn.itcast.service.impl.PersonServiceBean.*(..))中的cn.itcast.service.impl.PersonServiceBean是一个具体的类,后面又
加了“*”表示对前面定义的类中的所有方法。
(3)对AOP测试:
public class SpringAOPTest
{
@Test
public void interceptorTest()
{
ApplicationContext cxt=new ClassPathXmlApplicationContext("beans.xml");
PersonService service=(PersonService) cxt.getBean("personService");
service.save("xxx");
}
}
测试结果:
如果PersonServiceBean中save()方法里没有错误(不会抛出异常),则执行结果是:
前置通知
进入方法
我是save()方法
后置通知
最终通知
退出方法
如果PersonServiceBean中save()方法中有错误(抛出异常),则执行结果是:
前置通知
进入方法
最终通知
例外通知
(4)最后,我们还可以对注解方式实现AOP开发的功能详细扩充:
1. 获得拦截到的方法中的参数:
public void save(String name)
{
System.out.println("我是save()方法");
}
当前台执行上面的save(String name)方法时,动态代理MyInterceptor会拦截到save(String name)方法,如果我们想在动态代理中获取save(String name)方法里的参数(name),修改前置通知的表达式如下:
@Before("anyMethod()&&args(name)")
//args()中的参数名和doAccessCheck(String name)中参数名一致
private void doAccessCheck(String name)
{
System.out.println("前置通知"+name);
}
注解:@Before("anyMethod()&&args(name)")中的切点为两个限制条件:任何方法和名为name的参数。
注意:@Pointcut("execution(* cn.itcast.service.impl.PersonServiceBean.* (..))")中定义的参数是任意的,可有可无,可多可少,相当于全局变量;@Before("anyMethod()&&args(name)")相当于局部变量,当然局部变量优先于全局变量。
2. 获得拦截到的方法的返回结果:
public String getPersonName(Integer id)
{
System.out.println("我是getPersonName()方法");
return "xxx";
}
当前台执行上面的getPersonName()方法时,动态代理MyInterceptor会拦截到getPersonName()方法,如果我们想在动态代理中获取getPersonName()方法的返回值,修改后置通知的注解表达式如下:
@AfterReturning(pointcut="anyMethod()",returning="result")
public void doAfterReturning(String result)
{
System.out.println("后置通知"+result);
}
注解:
@AfterReturning()括号中默认的填写的切入点(spring中即方法),
@AfterReturning("anyMethod()")这里的写法已经有所省略,原本为:
@AfterReturning(pointcut="anyMethod()")
returning=""为设置返回值的名称,和doAfterReturning()方法中的参数名一致。
3. 获得例外通知的异常
public void save(String name)
{
System.out.println(2/0);
System.out.println("我是save()方法");
}
当前台执行上面的save()方法时,肯定会发生异常,动态代理MyInterceptor会拦save()方法,如果我们想在动态代理中获取其异常信息,修改例外通知的注解表达式如下:
@AfterThrowing(pointcut="anyMethod()",throwing="e")
public void doAfterThrowing(Exception e)
{
System.out.println("例外通知"+e);
}
b) 基于xml方式进行AOP开发:
使用的接口和实现接口的类都使用上面的,只不过要将动态代理(MyInterceptor)的注解给删除掉:
public class MyInterceptor
{
public void anyMethod()
{}
public void doAccessCheck()
{
System.out.println("前置通知");
}
public void doAfterReturning()
{
System.out.println("后置通知");
}
public void doAfter()
{
System.out.println("最终通知");
}
public void doAfterThrowing()
{
System.out.println("例外通知");
}
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable
{
Object result = null;
// if ()判断用户是否拥有权限
// {
System.out.println("进入方法");
result = pjp.proceed();
System.out.println("退出方法");
// }
return result;
}
}
12.基于Spring注解方式管理事务与传播行为:
12.1 更改两类异认回滚与否的默认原则:
使用@Transactional标注在类的定义上方,能保证此类中方法里的整个操作是一个事务,即方法执行前打开事务,执行后关闭事务,遇到异常回滚事务,但是有两种异常(unchecked、checked)只有其中一种异常抛出时才会被回滚。(RuntimeException属于unchecked异常,Exception属于checked异常)
Unchecked:会回滚;
Checked:不会回滚。
举例说明unchecked也修改为可不会滚:
public void delete(Integer id)
{
jdbcTemplate.update("delete from person where id=?", new Object[]{id}, new int[]{java.sql.Types.INTEGER});
throw new RuntimeException("运行期异常");
}
Delete()方法抛出RuntimeException异常spring默认会回滚记录,导致原纪录还在。通过注解使得它抛出异常RuntimeException异常不会回滚,然后记录被删除:
@Transactional(noRollbackFor=RuntimeException.class)
public void delete(Integer id){……}
举例说明checked也修改为可会滚:
public void delete(Integer id)throws Exception
{
jdbcTemplate.update("delete from person where id=?", new Object[]{id}, new int[]{java.sql.Types.INTEGER});
throw new Exception("运行期异常");
}
Delete()方法抛出Exception异常spring默认不会回滚记录,导致原纪录被删除。通过注解使得它抛出Exception异常会回滚,然后记录还在。
@Transactional(rollbackFor=Exception.class)
public void delete(Integer id)throws Exception{……}
12.2 关闭不必要的方法事务
某些方法不必要开启事务服务,好比查询,根本不会涉及到数据数据的变动,所以不必要开启耗资源的事务,可以使用注解关闭事务:
@Transactional(propagation=Propagation.NOT_SUPPORTED)
public Person getPerson(Integer id){……}
propagation类型值还有很多,现在分个解释:
REQUIRED:业务方法需要在一个事务中运行。如果方法运行时,已经在一个事务中,那么假如到该事物,否则为自己创建一个新的事务;
REQUIRESENEW:属性表明不管是否存在事务,业务方法总会为自己发起一个新的事务。如果方法已近运行在一个事务中,则原有的事务会被挂起,新的事务会被创建,知道方法结束,新的事务才算结束,原先的事务才会恢复执行;
MANDATORY:该属性指定业务方法只能在一个已经存在的事务中执行,业务方法不能发起自己的事务。如果业务方法在没有食物的环境下调用,容器就会抛出例外。
SUPPORTS:这以实物属性表明如果业务方法在某个事务范围内被调用,则方法成为该事务的一部分,如果业务方法在事务范围外被调用,则方法在没有事务的环境下执行;
NEVER:指定业务方法绝对不能再事务范围内执行。如果业务方法在某个事物中执行,容器抛出例外,只有业务方法没有关联到任何事务才能正常执行;
NESTED:如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行,它使用了一个单独的事务,这个事务拥有多个可以回滚的保存点,内部事务的回滚不会对外部造成影响,它只对DataSourceTransactionManager事务管理器起效。
12.3通过注解方式设置事务级别:
@Transactional(isolation=Isolation.READ_COMMITTED)
public Person getPerson(Integer id)
{
//具体实现省略
}
数据库系统提供了四种事务隔离级别供用于不同的锁类型来实现,在四中隔离级别中,Serializable的隔离级别最高,Read Uncommited的隔离级别最低。大多数据库默认的隔离界别为Read Commited,如SQL Server。当然也有少部分数据库默认的隔离级别为Repeatable Read,如MySQL:
· Read Uncommited: 读未提交的数据(会出现脏读,不可重复读和幻读);
· Read Commited:读已提交的数据(会出现不可重复读和幻读);
· Repeatable Read:可重复读(会出现幻读)
· Serializable:串读。
脏读:一个事务读取到另一个事务未提交的更新数据。
不可重复读:在同一事务中,多次读取同一数据返回的结果有所不同。换句话说就是:后续读取可以读到另一事务一提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时候,,能够保证所读数据一样,也就是,后续读取不能读到另一事务已提交的更新数据。
幻读:一个事务读取到另一事务已提交的insert数据。