作者:RodJohnson是Spring框架的创始人,著名作者。Rod在悉尼大学不仅获得了计算机学位,更令人吃惊的是在回到软件开发领域之前,他还获得了音乐学的博士学位。有着相当丰富的C/C++技术背景的Rod早在1996年就开始了对Java服务器端技术的研究。
Spring框架是一个分层架构,由7个定义良好的模块组成。Spring模块构建在核心容器之上,核心容器定义了创建、配置和管理bean的方式。
组成Spring框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合实现。每个模块的功能如下:
//定义一个汽车接口publicinterfaceCar{/***所有的汽车都可以开*/voiddriver();}//创建两辆车实现car接口重写接口里面的方法publicclassBenZimplementsCar{@Overridepublicvoiddriver(){System.out.println("开奔驰车");}}publicclassBmwimplementsCar{@Overridepublicvoiddriver(){System.out.println("开宝马车");}}比如我们需要两辆车,如果按照我们之前学习的方法,我们会直接通过new创建对象。
publicclassUser{publicstaticvoidmain(String[]args){1.自己创建对象CarbenZ=newBenZ();Carbmw=newBmw();//2.自己消费对象benZ.driver();bmw.driver();}}但是有的时候需要一个司机来给我们产生对象,我们只负责消费对象,而不考虑对象是如何产生的。工厂模式承担的就是司机的角色。
所有我们定义了一个工厂类,负责创建对象。
publicclassBeanFactory{
1、可扩展性比较差,比如我们要加一辆车,BenaFactory工厂类就需要进行修改了,当业务量很多的时候,这个就会变成一场噩梦。
2、BenaFactory工厂类是依赖所有的实现类的,会造成耦合度过高
那么我们就需要进行解决:
1、当我们加新的类的时候不要去修改BeanFactory
2、BeanFactory类不能依赖Car的实现类
方法:
1、使用配置文件,配置客户汽车的实现类
2、在工厂类中,通过加载配置文件,根据配置文件内容,使用反射技术在运行时创建所需的对象
配置文件:
bean.properties#通过配置文件指定名字与实现类的对应关系,键是名字,值是类全名benz=com.itheima.car.BenZbmw=com.itheima.car.Bmw改造BeanFactory工厂类:
1、读取配置文件
2、通过键得到相应的汽车的类全名
3、通过反射实例化汽车对象返回
publicclassAudiimplementsCar{@Overridepublicvoiddriver(){System.out.println("开奥迪车");}}只需要增加新的配置行
audi=com.itheima.car.Audi然后我们不要去改变工厂类,提高了可拓展性,创建者只需要调用工厂类传入参数即可
publicclassUser{publicstaticvoidmain(String[]args){//1.由别人(工厂类)创建对象CarbenZ=BeanFactory.getBean("benz");Carbmw=BeanFactory.getBean("bmw");Caraudi=BeanFactory.getBean("audi");//2.自己消费对象benZ.driver();bmw.driver();audi.driver();}}2.2Ioc的理论推导这里我们再来看另外一个差不多的例子,之前我们在开发web的时候,我们是怎么做项目的?
1、先写一个UserDao接口
publicinterfaceUserDao{publicvoidgetUser();}2、再去写Dao的实现类
publicclassUserDaoImplimplementsUserDao{@OverridepublicvoidgetUser(){System.out.println("获取用户数据");}}3、然后去写UserService的接口
publicinterfaceUserService{publicvoidgetUser();}4、最后写Service的实现类
publicclassUserServiceImplimplementsUserService{privateUserDaouserDao=newUserDaoImpl();@OverridepublicvoidgetUser(){userDao.getUser();}}5、测试一下
@Testpublicvoidtest(){UserServiceservice=newUserServiceImpl();service.getUser();}但是这种写法就会有的问题,当我们增加Userdao的实现类
publicclassUserDaoMySqlImplimplementsUserDao{@OverridepublicvoidgetUser(){System.out.println("MySql获取用户数据");}}这样的话我们就需要service实现类里面修改对应的实现
publicclassUserServiceImplimplementsUserService{privateUserDaouserDao=newUserDaoMySqlImpl();@OverridepublicvoidgetUser(){userDao.getUser();}}和上面的Car例子一样,如果我们需求很大的时候,这种方式根本就是不可能的,每次变动,都需要修改大量代码.这种设计的耦合性太高了,牵一发而动全身。
那这个问题该怎么解决呢?
我们可以在需要用到他的地方,不去实现它,而是留出一个接口,利用set,我们去代码里修改下。
publicclassUserServiceImplimplementsUserService{privateUserDaouserDao;//利用set实现publicvoidsetUserDao(UserDaouserDao){this.userDao=userDao;}@OverridepublicvoidgetUser(){userDao.getUser();}}在测试类里进行测试修改
@Testpublicvoidtest(){UserServiceImplservice=newUserServiceImpl();service.setUserDao(newUserDaoMySqlImpl());service.getUser();//那我们现在又想用Oracle去实现呢service.setUserDao(newUserDaoOracleImpl());service.getUser();}这样做就会发生根本性的变化,之前我们创建对象的时候都是自己去创建对象,就行上面的Car案例一样,我们自己去购买汽车,但是现在呢,我们只需要想着,要那种汽车,然后把主动权交给第三方,让他们帮我们去买,具体怎么买的,我们根本不需要考虑。程序也一样,我们去不用管怎么创建和怎么实现,它需要负责提供一个接口。
这种创建对象的权利,由程序代码主动去创建对象的方式,转变成了由第三方创建,然后提供给我们,我们使用到的时候再去容器中取,变成一种从主动到被动的方式,称之为控制反转,IoC(InversionOfControl)。
IoC从哪里获取对象?与传统方式创建对象有什么区别?
从容器中获取一个已经创建了的对象控制权发生了反转IoC在这里我们首先明确一个事情:Spring的IoC解决的问题,就是工厂模式解耦解决的问题。
这也是使用Ioc这一个思想的作用:
创建一个新的模块
编写持久层
编写CustomerDao接口,添加保存客户的方法voidsaveCustomer();
CustomerDao.java
/***DAO的接口*/publicinterfaceCustomerDao{/***添加一个客户*/voidsaveCustomer();}编写客户DAO实现类,实现接口中的方法,直接输出一句话"保存客户数据"
CustomerDaoImpl.java
publicclassCustomerDaoImplimplementsCustomerDao{/***添加一个客户*/@OverridepublicvoidsaveCustomer(){System.out.println("添加了一个客户");}}测试类
publicclassApp{publicstaticvoidmain(String[]args){//以前的写法://1.创建对象CustomerDaocustomerDao=newCustomerDaoImpl();//2.消费对象customerDao.saveCustomer();}}3.3搭建Spring入门开发环境添加Spring框架
项目结构
pom文件内容
publicclassApp{publicstaticvoidmain(String[]args){//现在的写法//1.从Spring容器中获取对象//1.1创建Spring的上下文对象:在类路径下读取配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//1.2从容器中获取对象CustomerDaocustomerDao=(CustomerDao)context.getBean("customerDao");System.out.println(customerDao.getClass());//2.消费对象customerDao.saveCustomer();}}执行结果
classcom.itheima.dao.impl.CustomerDaoImpl添加了一个客户4IoC容器:创建容器三种方式4.1BeanFactory容器的类结构4.2ApplicationContext接口常用实现类读取类路径下XML配置文件来创建容器
读取本地文件路径下XML配置文件来创建容器,通常使用绝对地址
注解方式创建容器
复制前一个项目,修改测试类
publicclassApp{publicstaticvoidmain(String[]args){//现在的写法//1.从Spring容器中获取对象//1.1创建Spring的上下文对象:在类路径下读取配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//1.2从容器中获取对象CustomerDaocustomerDao=(CustomerDao)context.getBean("customerDao");System.out.println(customerDao.getClass());//2.消费对象customerDao.saveCustomer();}}方式一:类路径配置文件创建容器
ClassPathXmlApplicationContext:
实例化ApplicationContext,没有获取CustomerDao对象之前,CustomerDao对象已经实例化。只要Spring容器加载,所有的对象都会被实例化。
//1.类路径加载容器,参数:类路径下配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");方式二:本地配置文件方式创建容器
FileSystemXmlApplicationContext:
//2.本地绝对路径加载容器,参数:文件的绝对路径ApplicationContextcontext=newFileSystemXmlApplicationContext("D:\\IdeaWork\\JavaEE138\\day43_03_Context\\src\\main\\resources\\applicationContext.xml");方式三:注解的方式创建容器
AnnotationConfigApplicationContext:
参数为CustomerDaoImpl.class类对象,注:这个类对象以后应该设置为包含注解的那个配置类
//3.使用注解的方式创建容器,参数:包含了注解的类名ApplicationContextcontext=newAnnotationConfigApplicationContext(CustomerDaoImpl.class);5Bean标签5.1Bean标签的作用作用:配置一个对象,放在Spring容器中,由Spring容器帮我们创建这个对象。
我们使用对象的时候,从容器中去获取就可以了。
代码:
applicationContext.xml
publicclassTestCustomer{publicstaticvoidmain(String[]args){//类路径加载容器,参数:类路径下配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//通过id获取对象CustomerDaocustomerDao1=(CustomerDao)context.getBean("customerDao");CustomerDaocustomerDao2=(CustomerDao)context.getBean("aaa");System.out.println(customerDao1);System.out.println(customerDao2);//两次获取同一个对象,地址是相同System.out.println(customerDao1==customerDao2);}}结果
创建客户DAO对象com.itheima.dao.impl.CustomerDaoImpl@5442a311com.itheima.dao.impl.CustomerDaoImpl@5442a311true5.3scope属性属性说明scope对象在容器中存在的存活周期singleton:单例对象,这个对象在容器中只有一个,这是默认的取值prototype:每次访问都会创建一个新的对象,对象用完就销毁用于Web开发:request:对象放在请求域中session:对象放在会话域中application:对象放在上下文域中globalsession:对象放在全局的会话域中,只用于分布式的开发关于GlobalSession的理解
用于分布式开发,一个项目部署到了多个服务器上
代码
publicclassTestCustomer{publicstaticvoidmain(String[]args){//类路径加载容器,参数:类路径下配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//通过id获取对象CustomerDaocustomerDao1=(CustomerDao)context.getBean("customerDao");CustomerDaocustomerDao2=(CustomerDao)context.getBean("customerDao");System.out.println(customerDao1);System.out.println(customerDao2);//两次获取同一个对象,地址是相同System.out.println(customerDao1==customerDao2);}}结果
创建客户DAO对象com.itheima.dao.impl.CustomerDaoImpl@5442a311com.itheima.dao.impl.CustomerDaoImpl@5442a311true5.3.2scope取值prototype代码
创建客户DAO对象创建客户DAO对象com.itheima.dao.impl.CustomerDaoImpl@5622fdfcom.itheima.dao.impl.CustomerDaoImpl@4883b407false5.3.3init-method和destroy-method属性属性说明init-method创建对象以后,初始化调用的方法,指定初始化方法的名字destroy-method指定销毁对象前,调用方法的名字代码
1、改造客户dao实现类,增加初始化和销毁方法,名字随意
注:销毁的方法只在单例模式下起作用
publicclassCustomerDaoImplimplementsCustomerDao{//对象实例化就会调用publicCustomerDaoImpl(){System.out.println("创建客户DAO对象");}/***添加一个客户*/@OverridepublicvoidsaveCustomer(){System.out.println("添加了一个客户");}publicvoidinit(){System.out.println(this+"初始化的方法");}publicvoiddestroy(){System.out.println(this+"对象销毁的方法");}}2、配置applicationContext.xml文件,增加init-method和destroy-method属性。
publicclassTestCustomer2{publicstaticvoidmain(String[]args){//类路径加载容器,参数:类路径下配置文件ClassPathXmlApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//通过id获取对象CustomerDaocustomerDao1=(CustomerDao)context.getBean("customerDao");//必须主动关闭容器才会激活对象中销毁的方法,关闭的方法在容器的子类中context.close();}}4、结果
创建客户对象com.itheima.Impl.CustomerDaoImpl@548a9f61初始化方法com.itheima.Impl.CustomerDaoImpl@548a9f61对象销毁的办法5.3.4单例延迟加载属性说明lazy-init是否使用延迟加载,默认是不执行延迟加载延迟加载只用于:scope="singleton",使得只有获取对象的时候才去创建对象,相当于scope="prototype"。
scope="singleton",默认值,即默认是单例scope="prototype",多例2、scope
scope="singleton"在容器启动就创建,容器关闭才销毁scope="prototype"在用到对象的时候,才创建好对象3、是否延迟创建
lazy-init="false"默认为false,不延迟创建,即在启动时候就创建对象lazy-init="true"延迟初始化,在用到对象的时候才创建对象(只对单例有效)4、创建对象之后,初始化/销毁
init-method对象的初始化犯法,在构造方法之前执行destory-method对象销毁之前执行6创建对象的三种方式6.1无参构造方法步骤
注:默认使用无参数构造方法创建对象。如果此时没有无参构造方法,创建对象会失败
publicclassTestCustomer{publicstaticvoidmain(String[]args){//类路径加载容器,参数:类路径下配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//通过id获取对象CustomerDaocustomerDao=(CustomerDao)context.getBean("customerDao");System.out.println("对象:"+customerDao);}}6.2静态工厂方法概述
该方式要求开发者创建一个静态工厂的方法来创建Bean的实例
创建静态工厂类
/***工厂类*/publicclassStaticFactory{/***编写一个静态方法创建对象*/publicstaticCustomerDaogetBean(){returnnewCustomerDaoImpl();}}配置文件
得到customerDao对象,并且打印输出
publicclassTestCustomer{publicstaticvoidmain(String[]args){//类路径加载容器,参数:类路径下配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//通过id获取对象CustomerDaocustomerDao=(CustomerDao)context.getBean("customerDao");System.out.println("对象:"+customerDao);}}6.3实例工厂方法步骤
1.需要在容器中使用Bean配置实例工厂对象,并且指定id。
2.在配置文件中,需要实例化的Bean通过factory-bean属性指向配置的实例工厂的id。
3.使用factory-method属性指定使用工厂中的哪个方法
实例工厂创建对象
/***实例工厂*/publicclassInstanaceFactory{/***通过实例方法去创建对象*/publicCustomerDaogetBean(){returnnewCustomerDaoImpl();}}配置文件
得到customerDao对象
publicclassTestCustomer{publicstaticvoidmain(String[]args){//类路径加载容器,参数:类路径下配置文件ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//通过id获取对象CustomerDaocustomerDao=(CustomerDao)context.getBean("customerDao");System.out.println("对象:"+customerDao);}}7依赖注入依赖注入就是由Spring创建对象,并且给成员变量赋值。类似于前面工厂模式的例子。
1、创建构造方法注入的类Customer
publicclassCustomer{privateIntegerid;privateStringname;privateBooleanmale;privateDatebirthday;/***如果类中存在有参的构造方法,必须创建无参的构造方法,否则会导致Spring创建对象失败*/publicCustomer(){}publicCustomer(Integerid,Stringname,Booleanmale,Datebirthday){this.id=id;this.name=name;this.male=male;this.birthday=birthday;}@OverridepublicStringtoString(){return"Customer{"+"id="+id+",name='"+name+'\''+",male="+male+",birthday="+birthday+'}';}}2、配置applicationContext.xml
通过构造方法注入属性:constructor-arg
子元素的属性:1)index:构造方法的索引,从0开始
2)value:属性的值,简单类型使用value属性---简单类型=八大类型+String类型
3)ref:引用类型的值
4)name:指定构造方法形参的名字
5)type:指定变量的数据类型
1、给Customer类添加set方法
publicclassCustomer{privateIntegerid;privateStringname;privateBooleanmale;privateDatebirthday;publicIntegergetId(){returnid;}publicvoidsetId(Integerid){this.id=id;} ~~~~省略@OverridepublicStringtoString(){return"Customer{"+"id="+id+",name='"+name+'\''+",male="+male+",birthday="+birthday+'}';}}2、配置applicationContext.xml,通过property元素给所有的属性赋值
概述
本质上就是set注入,只是简化了而已
注:先要在xml中导入p命名空间
本质仍然是调用类中的set方法实现注入功能。
步骤
1、创建实体类,Person
publicclassPerson{privateString[]array;//字符串数组privateList
组件扫描和自动装配组合发挥巨大威力,使得显示的配置降低到最少。
推荐不使用自动装配xml配置,而使用注解,这里先讲解xml配置,注解的实现方式跟着注解一起讲。
1、新建一个项目
2、新建两个实体类,CatDog都有一个shout的方法
publicclassCat{publicvoidshout(){System.out.println("miao~");}}publicclassDog{publicvoidshout(){System.out.println("wang~");}}3、新建一个用户类User
publicclassUser{privateCatcat;privateDogdog;privateStringstr;}4、编写Spring配置文件
publicclassMyTest{@TestpublicvoidtestMethodAutowire(){ApplicationContextcontext=newClassPathXmlApplicationContext("beans.xml");Useruser=(User)context.getBean("user");user.getCat().shout();user.getDog().shout();}}7.5.1xml配置byNameautowirebyName(按名称自动装配)
由于在手动配置xml过程中,常常发生字母缺漏和大小写等错误,而无法对其进行检查,使得开发效率降低。
采用自动装配将避免这些错误,并且使配置简单化。
1、修改bean配置,增加一个属性autowire=“byName”
3、我们将cat的beanid修改为catXXX
4、再次测试,执行时报空指针java.lang.NullPointerException。因为按byName规则找不对应set方法,真正的setCat就没执行,对象就没有初始化,所以调用时就会报空指针错误。
小结
当一个bean节点带有autowirebyName的属性时。
autowirebyType(按类型自动装配)
使用autowirebyType首先需要保证:同一类型的对象,在spring容器中唯一。如果不唯一,会报不唯一的异常。
NoUniqueBeanDefinitionException代码
1、将user的bean配置修改一下:autowire=“byType”
2、测试,正常输出
3、在注册一个cat的bean对象!
5、删掉cat2,将cat的bean名称改掉!测试!因为是按类型装配,所以并不会报异常,也不影响最后的结果。甚至将id属性去掉,也不影响结果。
这就是按照类型自动装配!
SpringIoC容器中XML配置与注解可以混合使用。即:如果Dao用注解创建的对象;service用xml创建的对象一样可以注入DAO。
@Component注解:我们之前都是使用bean的标签进行bean注入,但是实际开发中,我们一般都会使用注解!
注:在配置文件中指定Spring容器扫描的基包:扫描这个包和它的子包。
如果要使用这个标签,必须要导入context命名空间。
1、指定需要扫描的基包
@ComponentpublicclassAccount{privateIntegerid;//编号privateStringname;//姓名privateDoublemoney;//余额@OverridepublicStringtoString(){return"Account{"+"id="+id+",name='"+name+'\''+",money="+money+'}';}}3、测试类
publicclassTestAccount{publicstaticvoidmain(String[]args){//创建容器ClassPathXmlApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");//从容器中获取对象Accountaccount=(Account)context.getBean("account");System.out.println(account);}}其它的注解:@Controller@Service@Repository
语义化:这三个标签的功能与@Component是一样的,只是语义上的区别
@Autowired注解可以出现的位置:
@Autowired注解的作用
1、创建User1对象,包含字符串的属性username,有toString()方法添加@Component注解,注定注解名字为user,给username添加@Autowired注解
@Component("user")publicclassUser1{@AutowiredprivateStringusername;@OverridepublicStringtoString(){return"User{"+"username='"+username+'\''+'}';}}2、在applicationContext.xml中添加一个id为man的字符串。在子标签中,使用构造器注入值。
user1{username='NewBoy'}4、在applicationContext.xml中再添加一个id为username的字符串,再运行测试,则注入新的值。
1、将User1复制成User2对象,去掉User1中的@Component,创建方法input(Stringusername),接收注入的值。给input方法添加@Autowired注解。
@Component("user")publicclassUser2{privateStringusername;@Autowiredpublicvoidinput(Stringusername){this.username=username;}@OverridepublicStringtoString(){return"user1{"+"username='"+username+'\''+'}';}}2、在applicationContext.xml中添加一个id为man的字符串。在子标签中,使用构造器注入值。
输出报错,抛出异常小结:
@Autowired的作用
@Autowired的required属性默认是true,这个属性必须要注入。如果没有注入这个属性,就会抛出异常
1、在@Autowired后面设置required=false;
@Component("user")publicclassUser2{privateStringusername;@Autowired(required=false)publicvoidinput(Stringusername){this.username=username;}@OverridepublicStringtoString(){return"user1{"+"username='"+username+'\''+'}';}}2、在applicationContext.xml中删除注入的值
user1{username='null'}@Qualifier注解作用
在属性上使用
1、在applicationContext.xml中创建id为man的字符串值为"孙悟空";创建id为woman的字符串,值为"白骨精"。
@Component("user")publicclassUser3{@Autowired@Qualifier("women")privateStringusername;/*publicvoidinput(Stringusername){this.username=username;}*/@OverridepublicStringtoString(){return"user1{"+"username='"+username+'\''+'}';}}3、运行测试
user1{username='白骨精'}在方法上使用
1、在publicvoidinput(Stringusername)方法上同时使用@Autowired和@Qualifier("man")
@Component("user")publicclassUser3{privateStringusername;@Autowired@Qualifier("man")publicvoidinput(Stringusername){this.username=username;}@OverridepublicStringtoString(){return"user1{"+"username='"+username+'\''+'}';}}2、运行测试
user1{username='孙悟空'}小结
@Qualifier注解的作用
从容器中按byName匹配的方式去查找对象,并且注入到属性或方法中,不能单独使用。
作用
1、设置属性cat的@Resource(name="cat2"),dog的@Resource
@ComponentpublicclassUser{@Resource(name="cat2")privateCatcat;@ResourceprivateDogdog;privateStringstr;}2、applicationContext.xml
wang~miao~4、删掉applicationContext.xml中的cat2,实体类上只保留注解
@value作用
1、如果没有set方法,直接在属性上面赋值
@Value("true")privateBooleansex;2、如果由set方法,需要set方法上面赋值
@Value("白骨精")publicvoidsetName(Stringname){this.name=name;}代码
1、给对象添加Stringname,booleansex,java.util.Datebirthday三个属性,使用@Value注入不同类型的值,其中生日使用"yyyy/MM/dd"的格式
@Component("user")publicclassUser4{privateStringname;@Value("true")privateBooleansex;@Value("1999/12/15")privateDatebirthday;@Value("白骨精")publicvoidsetName(Stringname){this.name=name;}@OverridepublicStringtoString(){return"User4{"+"name='"+name+'\''+",sex="+sex+",birthday="+birthday+'}';}}}2、运行测试
User4{name='白骨精',sex=true,birthday=WedDec1500:00:00CST1999}小结
@Value注解的作用:用于注入简单类型
如果由set方法,需要在set方法上面赋值
JdbcTemplate是Spring提供的一个模板类,它是对jdbc的封装。用于数据库持久层的操作,它的特点是:简单、方便。
它简化了JDBC的使用,并有助于避免常见错误。它执行核心的JDBC工作流程,我们只需要写SQL语句,并且从中获取结果就可以了。
API的介绍
RowMapper接口的实现类
RowMapper的映射规则
1.表的字段名与类中的属性名相同,表的字段名大小写不区分。
2.表的字段名如果有多个单词使用下划线隔开,与Java中驼峰命名的属性相对应。
查询一条记录
查询多条记录
API说明
需求
查询所有男生的学生信息
执行结果
Student{id=1,username='孙悟空',birthday=1980-10-24,sex='男',address='花果山水帘洞'}Student{id=3,username='猪八戒',birthday=1983-05-20,sex='男',address='福临山云栈洞'}9.4使用IoC管理JdbcTemplate步骤
Student{id=1,username='孙悟空',birthday=1980-10-24,sex='男',address='花果山水帘洞'}Student{id=2,username='白骨精',birthday=1992-11-12,sex='女',address='白虎岭白骨洞'}Student{id=3,username='猪八戒',birthday=1983-05-20,sex='男',address='福临山云栈洞'}Student{id=4,username='蜘蛛精',birthday=1995-03-22,sex='女',address='盤丝洞'}9.5JdbcTemplate使用第三方连接池需求
类与类之间的依赖关系
创建maven项目
pom.xml文件
省略创建表结构
droptableifexistsaccount;--创建数据表,账户表CREATETABLEaccount( idINTPRIMARYKEYAUTO_INCREMENT, nameVARCHAR(10), moneyDOUBLE--金额);--添加数据INSERTINTOaccount(name,money)VALUES('周瑜',1000),('小乔',1000);select*fromaccount;创建实体类
/**账户实体类*/publicclassAccount{privateIntegerid;privateStringname;//名字privateDoublemoney;//余额//javabean省略}DAO接口和实现类
jdbc.username=rootjdbc.password=rootjdbc.url=jdbc:mysql://localhost:3306/springcharacterEncoding=utf8jdbc.driverClassName=com.mysql.jdbc.Driver2.使用context:property-placeholder,加载类路径下的配置文件,需要导入context命名空间。
回顾:配置的对比
使用XML+注解的方式改造上面的账户案例
DAO实现类
/***DAO的实现类*/@RepositorypublicclassAccountDaoImplimplementsAccountDao{//按类型匹配的方式从容器中查找,不需要set方法@AutowiredprivateJdbcTemplatejdbcTemplate;}业务层实现类
/***业务层实现类,调用DAO中方法注:要执行名字*/@Service("accountService")publicclassAccountServiceImplimplementsAccountService{@AutowiredprivateAccountDaoaccountDao;}applicationContext.xml配置
第三方类使用配置,自己写的类使用注解10.5纯注解配置:分析上面的案例中我们使用了基于注解和xml的配置,虽然简化了代码,但是大家就会发现一个问题,我们也就会使用到xml文件。
@Bean注解的作用
作用:从类路径下加载属性文件
value[]属性:指定要加载的一个或多个属性文件
注:@PropertySource可以不用写classpath,因为注解默认从类路径下加载
作用:导入其他的配置类
属性:导入配置类的名字
我们已经配置好了要扫描的包,但是数据源和JdbcTemplate对象如何从配置文件中移除呢?
我们可以创建配置类,用来实现注解方式,并且为了模块化设计,将与数据库有关的配置写到另一个配置类中:JdbcConfig
创建数据源有关的配置类
JdbcConfig
1、创建JdbcConfig配置类,这个文件通过SpringConfig类导入,可以省略@Configuration注解。
2、使用@PropertySource加载druid.properties配置文件。
4、创建数据源的方法获取数据源,注如创建模板对象的方法,返回模板对象。
注:在每个方法上添加@Bean注解,无需指定value,方法通过类型匹配的方式从容器中去查找对象。同时自动将方法返回的对象加入到容器中,方法名就是容器中的id。
/***持久层的配置类*PropertySource读取类路径下属性文件*/@PropertySource("druid.properties")publicclassJdbcConfig{//将属性文件中的值注入给成员变量@Value("${jdbc.driverClassName}")privateStringdriverClassName;@Value("${jdbc.username}")privateStringusername;@Value("${jdbc.password}")privateStringpassword;@Value("${jdbc.url}")privateStringurl;/***创建数据源的方法*Bean将方法的返回值放在容器中*/@BeanpublicDataSourcecreateDataSource(){DruidDataSourceds=newDruidDataSource();ds.setDriverClassName(driverClassName);ds.setUsername(username);ds.setPassword(password);ds.setUrl(url);returnds;}@BeanpublicJdbcTemplatecreateJdbcTemplate(DataSourceds){returnnewJdbcTemplate(ds);}}SpringConfig
注:类中没有任何其它的代码
@Configuration//这是一个配置类@ComponentScan("com.itheima")//指定要扫描基包@Import(JdbcConfig.class)//导入其它的配置类publicclassSpringConfig{}测试类
容器通过:AnnotationConfigApplicationContext类创建
publicclassTestAccount{//只创建一个容器对象privatestaticAnnotationConfigApplicationContextcontext;//创建一个业务对象privateAccountServiceaccountService;/*@BeforeClass是junit中注解,放在静态方法上在测试类加载以后执行1次*/@BeforeClasspublicstaticvoidinit(){context=newAnnotationConfigApplicationContext(SpringConfig.class);}//在每个测试方法前调用1次@Beforepublicvoidbefore(){//从容器中获取数据accountService=(AccountService)context.getBean("accountService");}@TestpublicvoidtestFindAll(){List
ApplicationContextcontext=newClassPathXmlApplicationContext("applicationContext.xml");AccountServiceaccountService=(AccountService)context.getBean("accountService");如何去掉这些代码,使用自动注入的方式获取业务对象呢?
理想的状态
解决思路分析
针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建Spring容器,我们就无须手动创建了,问题也就解决了。
我们都知道,JUnit单元测试它自己无法知晓我们是否使用了Spring框架,更不用说帮我们创建Spring容器了。不过好在,JUnit给我们提供扩展的一个注解@RunWith,可以让我们替换掉它自己本身的运行器。这时,我们使用Spring框架提供的运行器,可以读取配置文件(或注解)来创建Spring容器。
1、添加spring-test的依赖包
//使用JUnit的注解@RunWith,让JUnit使用Spring提供的运行器@RunWith(SpringJUnit4ClassRunner.class)//使用locations="classpath:applicationContext.xml"指定XML的配置文件(locations的别名是value)。@ContextConfiguration("classpath:applicationContext.xml")publicclassTestAccount2{//给测试类中业务对象使用@Autowired注解@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestFindAll(){List
//Junit使用Spring的运行器,帮我们创建Spring的容器@RunWith(SpringJUnit4ClassRunner.class)//使用Spring提供的注解@ContextConfiguration,使用classes=SpringConfig.class指定有注解配置的类对象@ContextConfiguration(classes=SpringConfig.class)publicclassTestAccount2{//给业务类使用@Autowired注解@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestFindAll(){List
代理模式的作用
1、对原来的类功能进行增强:对目标类增加新的功能,对目标类使用代理模式,可以在不改变原来类的情况下增加新的功能。
2、可以拦截原来类的方法,决定被代理的方法是否执行。
代码模式的实现方式
1、静态代理
2、动态代理
代理角色是程序在执行过程中生成的。
代理模式模型图
)
代理类是程序运行前准备好的代理方式被成为静态代理。
代码演示
1、定义一个Star接口,定义唱歌和表演的方法。
//明星接口publicinterfaceStar{ //唱歌方法voidsing(Integermoney); //表演方法voidact();}2、创建一个LiuStar的类实现Star接口,重写所有方法
publicclassLiuStarimplementsStar{@Overridepublicvoidsing(Integermoney){System.out.println("刘德华唱歌真好听,收费:"+money);}@Overridepublicvoidact(){ System.out.println("刘德华拍戏棒棒哒!");}}3、创建一个LiuStarProxy的代理类,同样实现Star接口,创建LiuStar的类对象。
publicclassLiuStarProxyimplementsStar{ Starstar=newLiuStar(); @Override publicvoidsing(Integermoney){ if(money>10000){ star.sing(money); }else{ System.out.println("档期忙!"); } } @Override publicvoidact(){ System.out.println("刘德华拍戏棒棒哒!超级喜欢看"); }}4、测试
publicclassLiuStarProxyTest{ @Test publicvoidsing(){ newLiuStarProxy().sing(10001); newLiuStarProxy().act(); }}总结:
特点:
缺点:
代理类在程序运行时创建的代理方式被称为动态代理
使用动态代理可以很好的解决静态代理带来的问题。
代理的对象做到以下的一些功能:
1、可以针对某个具体的方法进行修改,其他不需要修改的方法可以保持不变。
2、接口拓展方法后,我们只需要修改真实对象方法即可。
对于调用者来说,他们调用的是代理对象得到方法,但是感觉上还是调用真实对象的方法。
这就是动态代理的好处。
JDK动态代理代码
JDK中为我们我们提供的一个动态代理的类,Proxy类;
Star接口和真实对象类和上面的静态代理一模一样,因为代理对象是在程序执行的过程中动态生成的,这里我们只需要在测试类中进行代码修改即可。
1、创建真实对象的对象,调用方法传入参数输出,查看结果。
2、调用Proxy.newProxyInstance方法,生成代理对象,对sing方法进行增强,如果满足条件,则改变方法内容,如果没有满足条件,则调用原来的方法。
3、调用生成的代理对象方法,查看结果。
4、使用instanceof方法判断s1是否是s2的实例,或者其子类、实现类的实例。然后用getClass方法获取class对象。
刘德华唱歌真好听,收费:500刘德华拍戏棒棒哒!超级喜欢看-----------刘德华唱歌真好听,收费:1500刘德华拍戏棒棒哒!超级喜欢看flaseclasscom.sun.proxy.$Proxy012.3CGLIB动态代理使用cglib动态代理的原因
特点:cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类或实现java接口,它广泛的被许多AOP的框架使用,为他们提供方法的拦截。
如果一个类并没有实现任何的接口,则无法使用上面所说的JDK动态代理,这时需要使用CGLIB代理,本质上是对原有类的继承,子类重写相应的方法。
cglib应用
定义一个拦截器。在调用目标方法时,cglib会回调MethodInterceptor接口方法拦截,来实现自己的代理逻辑,类似JDK的InvocationHandler接口。
这里的Enhancer类是cglib中的一个字节码增强器,它可以方便的对你想要处理的类进行扩展。
cglib代码
其他的代码还是和JDK代理中一样,我们只需要修改测试类中的代码。
刘德华唱歌真好听,收费:500刘德华拍戏棒棒哒!超级喜欢看-----------没有档期刘德华拍戏棒棒哒!超级喜欢看trueclasscom.itheima.cglib.LiuStar$$EnhancerByCGLIB$$38d4220212.4Spring中代理方式的说明概念:AOP面向切面编程,OOP面向对象编程
引入案例
比如银行系统会有一个取款流程,传统程序的流程是这样的:
另外系统还会有一个查询余额流程:
把这两个流程放到一起,我们会发现两者有一个相同的验证流程。
既然验证用户的功能是一样的,但是他们却出现在不同的地方,我们是不是可以把它提取出来,做成一个切面类在程序执行的时候动态的添加到业务程序中去,这个切面类可以在不同的地方重用,这叫面向切面编程。
基本概念
AOP(AspectOrientedProgramming),即面向切面编程。
有了AOP之后,我们在写功能模块代码时,类似验证用户这种代码,我们不需要写进去,只需要写取款和显示余额的业务代码。
然后在另一个地方,创建一个切面类,把验证代码的方法写进这个类,以后当执行取款和显示余额的时候,我们可以利用代理模式,将验证用户的功能在执行取款和显示余额前调用。
AOP中类与切面的关系
AOP的本质是在一系列纵向的控制流程中,把那些相同的子流程(如:验证用户)提取成一个横向的面。
然后将分散在主流程中相同的代码提取出来,写入这个横向的面,然后在程序编译或运行时,将这些提取出来的切面代码应用到需要执行的地方。
比如:"取款,查询,转账"前都要进行验证用户,则验证用户就可以做成切面类。在执行"取款,查询,转账"的操作时,由Spring容器将验证用户的代码织入到它们的前面,从而达到验证用户的目的。
而验证用户的代码只需要编写一次,我们也可以将编程的精力放在"取款,查询,转账"的主要业务上,这样就大大减少了代码量,提高了编程的速率。
常用场景
如:通过切面可以分别在类1和类2方法中加入了事务,日志,权限控制等功能。
AOP术语
AOP作用及优势
这里,有的同学会有一些疑问了,Java一旦写好,编译成Java.class后运行,我们想对类进行修改时很困难的,有些同学会想到用反射,但是注意啦,反射只能查看类的信息,不能修改类的内容。
而而AOP要求的恰恰就是在不改变业务类的源代码(其实大部分情况下你也拿不到)的情况下,修改业务类的方法,进行功能的增强。
那么怎么办才能做到呢?Spring已经给我们提供了支持。
1、因为Spring中AOP代理由Spring中的IOC容器负责管理的,其依赖关系也由IOC容器负责管理,因此,AOP代理可以直接使用容器中的其它bean实例作为目标,这种关系可由IOC容器的依赖注入提供。
2、在编译的时候,根据AOP的配置信息,悄悄的把日志,安全,事务等“切面”代码和业务类编译到一起去。
3、在运行期,业务类加载以后,通过Java动态代理技术为业务类生产一个代理类,把“切面”代码放到代理类中,Java动态代理要求业务类需要实现接口才行。
4、在运行期,业务类加载以后,动态的使用字节码构建一个业务类的子类,将“切面”逻辑加入到子类当中去,CGLIB就是这么做的。
注:当需要代理的类不是代理接口的时候,Spring会切换为使用CGLIB代理,也可强制使用CGLIB代理。
当向数据库中保存账户的时候,使用日志记录下这次的操作
面向切面编程的流程
1.开发业务类:添加账户
2.开发切面类:记录日志
3.使用AOP将业务类与切面类织入到一起,实现需求的功能
代码实现
voidsave()保存账户的方法
packagecom.itheima.service;/***账户业务接口*/publicinterfaceAccountService{/***保存账户*/voidsave();}实现业务接口类,输出"保存账户"
packagecom.itheima.service.impl;importcom.itheima.service.AccountService;publicclassAccountServiceImplimplementsAccountService{/***保存账户*/@Overridepublicvoidsave(){System.out.println("添加账户");}}3、创建LogAspect切面类,编写通知方法voidprintLog(),输出:"执行添加操作"
记录日志的工具类
packagecom.itheima.utils;importjava.sql.Time;/***切面类*/publicclassLogAspect{/***输出日志信息*/publicvoidprintLog(){System.out.println(newTime(System.currentTimeMillis())+"执行了保存操作");}}AOP流程分析
4、applicationContext.xml配置文件
XML中关于AOP的配置
注:在导入aop的命名空间,idea可以自动导入。
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")publicclassTestAccount{@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestSave(){System.out.println(accountService.getClass());accountService.save();}}执行效果
classcom.sun.proxy.$Proxy1508:56:00执行了保存操作保存账户13.4AspectJ表达式语言作用
切入点表达式的作用:指定哪些业务或方法需要织入,用来制定切入的规则
切面函数
execution表达式语法
1.格式:切点函数(访问修饰符返回值类型包名.类名.方法(参数))
2.问号表示可以出现或不出现,划线部分为必须的内容:返回值类型方法名参数列表
3.方法中参数个数
()没有参数(*)1个或多个参数(..)0个或多个参数4.类全名的包
..表示当前包和子包.表示当前包例子
execution(publicvoidcom.itheima.service.impl.AccountServiceImpl.save())精确匹配,写出了所有的规则execution(*com.itheima.service..*.*(String))任意的返回类型,service包和子包下所有的类所有的方法,方法的参数的类型是String类型execution(**(..))任意的返回类型,所有的类和方法,参数是0~多个execution(publicint*(..))访问修饰符是public,方法的返回类型是int,所有类和方法,任意多个参数execution(*save(..))||execution(*update(..))方法名是save或update的方法!execution(*save(..))除了save方法之外的方法within(com.itheima.*)com.itheima包下所有的类和所有的方法within(com.itheima..*)com.itheima包和子包中所有的类和所有方法bean(accountService)从容器中找一个accountService的idbean(*Service)从容器中找所有的以Service结尾的id代码演示
1、复制上面XML配置保存账户案例的项目
2、修改AccountService接口,创建方法intupdate(Stringname),并且实现方法
AccountService接口
/***账户业务接口*/publicinterfaceAccountService{/***保存账户*/voidsave();/***更新某个账户*/intupdate(Stringname);}AccountServiceImpl实现类
/***主要业务*/publicclassAccountServiceImplimplementsAccountService{/***保存账户*/@Overridepublicvoidsave(){System.out.println("添加账户");}/***更新某个账户*@paramname*/@Overridepublicintupdate(Stringname){System.out.println("更新"+name+"账户");return100;}}3、修改测试类,同时调用save()和update()方法
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")publicclassTestAccount{@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestSave(){accountService.save();System.out.println("=============");}@TestpublicvoidtestUpdate(){//获取业务对象,执行业务方法accountService.update("李四");System.out.println("===========");}}4、分别修改applicationContext.xml中切入点表达式,执行不同的效果
切入点表达式
execution(*com.itheima.service.impl.*.*(..))execution(*com.itheima.service..*.*(String))execution(*update*(..))execution(publicint*(..))execution(*save(..))||execution(*update(..))!execution(*save(..))within(com.itheima..*)bean(accountService)5、applicationContext.xml代码
通知类型介绍
伪代码
1.给LogAspect日志记录类添加:前置,后置,异常,最终通知的方法
2.在applicationContext.xml配置文件中配置上面的通知,分别查看不同的结果
1、复制上一个案例的模块代码
2、修改LogAspect中的方法
/***次要的业务:切面类*/publicclassLogAspect{publicvoidbefore(){System.out.println("前置通知");}publicvoidafterReturn(){System.out.println("后置通知");}publicvoidafterThrowing(){System.out.println("异常通知");}publicvoidafter(){System.out.println("最终通知");}}3、修改applicationContext.xml
如果账户名为NewBoy,则抛出运行时异常:"余额不足"
/***主要业务*/publicclassAccountServiceImplimplementsAccountService{/***更新某个账户*@paramname*/@Overridepublicintupdate(Stringname){if(name.equals("NewBoy")){thrownewRuntimeException("这是穷逼");}System.out.println("更新"+name+"账户");return100;}}5、测试类
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")publicclassTestAccount{@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestUpdate(){//获取业务对象,执行业务方法accountService.update("Jack");//accountService.update("NewBoy");System.out.println("===========");}}6、效果
传入的参数是:jack
前置通知更新Jack账户后置通知最终通知传入的参数是:NewBoy
前置通知异常通知最终通知13.7AOP编程:环绕通知——XML实现环绕通知的功能
环绕通知和其他通知的区别
1.可以决定是否调用目标方法
2.可以控制方法返回的对象值
ProceedingJoinPoint接口
Spring框架提供了ProceedingJoinPoint接口,作为环绕通知的参数。在环绕通知执行的时候,Spring框架会提供接口的对象,我们直接使用即可。
代码案例
1、复制上一个项目
2、删除AccountService中的update方法,修改接口AccountService中的save方法
/***账户业务接口*/publicinterfaceAccountService{intsave(intid,Stringname,doublemoney)throwsSQLException;}3、修改实现类AccountServiceImpl中的save方法
/***主要业务*/publicclassAccountServiceImplimplementsAccountService{@Overridepublicintsave(intid,Stringname,doublemoney)throwsSQLException{if(money<0){thrownewSQLException("金额不正常");}System.out.println("账户信息,编号:"+id+",名字:"+name+",金额:"+money);return1;}}4、在LogAspect类中写环绕通知方法
Objectaround(ProceedingJoinPointpoint)在方法中
环绕通知方法中输出
/***次要的业务:切面类*/publicclassLogAspect{/***环绕通知*@paramjoinPoint*@return*/publicObjectaround(ProceedingJoinPointjoinPoint){//1.获取目标方法的参数Object[]args=joinPoint.getArgs();System.out.println("参数:"+Arrays.toString());//2.获取当前执行的方法名System.out.println("方法名:"+joinPoint.getSignature().getName());//3.调用目标方法,可以拦截目标方法的执行Objectresult=null;try{//前置System.out.println("前置通知");result=joinPoint.proceed(args);//后置System.out.println("后置通知");}catch(Throwablethrowable){//异常throwable.printStackTrace();System.out.println("异常通知");}finally{//最终通知System.out.println("最终通知");}//4.输出方法返回值System.out.println("返回值:"+result);returnresult;}}5、applicationContext.xml配置环绕通知的配置
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")publicclassTestAccount{@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestUpdate(){//获取业务对象,执行业务方法try{intresult=accountService.save(1,"孙悟空",-1);System.out.println("接收返回值"+result);}catch(SQLExceptionex){ex.printStackTrace();}}}7、运行效果
使用金额大于0
参数:[1,孙悟空,1.0]方法名:save前置通知账户信息,编号:1,名字:孙悟空,金额:1.0后置通知最终通知方法返回值1接收的返回值1使用金额大于0
参数:[1,孙悟空,-1.0]方法名:save前置通知java.sql.SQLException:金额不正常结论:可以拦截方法不运行。
8、修改方法输入参数和返回值
publicclassLogAspect{publicObjectaround(ProceedingJoinPointjoinPoint){//1、获取目标方法的参数Object[]args=joinPoint.getArgs();System.out.println("参数:"+Arrays.toString(args));//修改参数数组的值args[1]="猪八戒";//2、获取当前执行的方法名System.out.println("方法名:"+joinPoint.getSignature().getName());//3、调用目标方法,可以拦截目标方法的执行Objectresult=null;try{System.out.println("前置通知");result=joinPoint.proceed(args);System.out.println("后置通知");}catch(Throwablethrowable){throwable.printStackTrace();System.out.println("异常通知");}finally{System.out.println("最终通知");}//4、输出方法返回值System.out.println("方法返回值"+result);//5、修改返回值return9999;}}9、执行效果
参数:[1,孙悟空,1.0]方法名:save前置通知账户信息,编号:1,名字:猪八戒,金额:1.0后置通知最终通知方法返回值1接收的返回值9999结论:修改方法的输入参数和返回值
AspectJ框架为AOP的实现提供了一套注解,用以取代applicationContext.xml文件中配置代码。
作用:开启自动代理的功能,使用AOP
1、复制AOP编程:前置、后置、异常、最终通知——XML实现的项目
2、修改AccountServiceImpl,使用@Service注解的方式,指定名字为accountService
packagecom.itheima.service.impl;importcom.itheima.service.AccountService;importorg.springframework.stereotype.Service;/***主要业务*/@Service//把业务类放在容器中publicclassAccountServiceImplimplementsAccountService{/***更新某个账户*@paramname*/@Overridepublicintupdate(Stringname){if(name.equals("NewBoy")){thrownewRuntimeException("这是穷逼");}System.out.println("更新"+name+"账户");return100;}}3、配置XML文件
1.切面类使用@Aspect和@Component
2..编写前置通知方法,使用注解:@Before("切点表达式")
3.编写后置通知:@AfterReturning("切点表达式")
4.编写异常通知:@AfterThrowing("切点表达式")
5.编写最终通知:注解:@After("切点表达式")
注:最终通知会在后置通知前面执行,但如果出现异常则先执行异常通知
5、使用@Pointcut注解优化切点表达式
1.方法没有返回值,方法体为空
2.使用注解,编写切入点表达式
3.在其它通知方法上引用这个方法名
packagecom.itheima.utils;importorg.aspectj.lang.annotation.*;importorg.springframework.stereotype.Component;/***次要的业务:切面类*/@Component//把这个类加到Spring容器中@Aspect//这个类是切面类publicclassLogAspect{@Pointcut("execution(*com.itheima.service..*.*(..))")//切面表达式,这是一个空方法,作用:创建切面表达式的publicvoidpt(){}@Before("pt()")publicvoidbefore(){System.out.println("前置通知");}@AfterReturning("pt()")publicvoidafterReturn(){System.out.println("后置通知");}@AfterThrowing("pt()")publicvoidafterThrowing(){System.out.println("异常通知");}@After("pt()")publicvoidafter(){System.out.println("最终通知");}}6、测试类
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")publicclassTestAccount{@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestUpdate(){//System.out.println(accountService.getClass());//获取业务对象,执行业务方法accountService.update("Jack");}}7、效果
正常执行输出后置通知
前置通知更新Jack账户最终通知后置通知出现异常输出异常信息
前置通知最终通知异常通知13.9AOP编程:环绕通知——注解实现注解说明@Around设置环绕通知代码实现
1、复制AOP编程:环绕通知——XML实现的项目
2、修改xml文件
在数据库中如果一个业务操作执行多次DML操作,所有的操作要全部成功,如果有其中一条语句出现异常,所有已经执行的代码需要进行回滚,要么全部成功,要么全部失败。
事务的特性
事务的隔离级别
MySQL默认是可重复读,Oracle和SQLServer是读已提交
Spring对事务的处理方式介绍
1.JavaEE体系进行分层开发,事务处理位于业务层
2.Spring框架为我们提供了一组事务控制的接口,这组接口在spring-tx-版本.RELEASE.jar中。
事务管理方式介绍
1.编程式事务管理
通过编写代码实现的事务管理,包括定义事务的开始,正常执行后事务提交和异常时的事务回滚。
1、事务管理器:PlatformTransactionManager
功能:事务的顶层接口,定义了:提交事务、获取事务、回滚事务的方法
该接口提供了操作事务的常用方法,方法不需要我们主动去调用,由Spring容器去调用。
事务管理器接口的实现类
2、事务定义对象:TransationDefinition
事务指定一个隔离级别,该隔离级别定义一个事务必须与由其他事务进行的资源或数据更改相隔离的程度。隔离级别从允许的并发副作用(例如,脏读或幻读)的角度进行描述。
3、事务状态:TransactionStatus
提供了获取事务状态的方法,事务是否是新的,事务是否完成,事务是否是只读等
案例需求
用户A要给用户B转账,使用SpringIoC+JdbcTemlate实现,通过增加来账户的钱或减少账户的钱来查看事务的执行情况。
没有事务的情况:代码实现
1、创建项目,导入依赖包,建表
publicclassAccount{privateIntegerid;privateStringname;privateDoublemoney; //javabean省略不写}//druid.propertiesjdbc.url=jdbc:mysql://localhost:3306/springcharacterEncoding=utf8jdbc.username=rootjdbc.password=rootjdbc.driverClassName=com.mysql.jdbc.Driver3、编辑配置文件applicationContext.xml
publicinterfaceAccountDao{/***更新账户*@paramname*@parammoney*/voidupdateAccount(Stringname,doublemoney);}@RepositorypublicclassAccountDaoImplimplementsAccountDao{@AutowiredprivateJdbcTemplatejdbcTemplate;/***更新账户*@paramname*@parammoney*/@OverridepublicvoidupdateAccount(Stringname,doublemoney){jdbcTemplate.update("UPDATEaccountSETmoney=money+WHERE`name`=",money,name);}}5、创建AccountService接口和实现类
publicinterfaceAccountService{/***转账的操作*@paramfromUser转出账户*@paramtoUser转入账户*@parammoney多少钱*/voidtransfer(StringfromUser,StringtoUser,doublemoney);}@ServicepublicclassAccountServiceImplimplementsAccountService{@AutowiredprivateAccountDaoaccountDao;/***转账的操作*@paramfromUser转出账户*@paramtoUser转入账户*@parammoney多少钱*/@Overridepublicvoidtransfer(StringfromUser,StringtoUser,doublemoney){//从一个账户扣钱accountDao.updateAccount(fromUser,-money);//另一个账户加钱accountDao.updateAccount(toUser,money);}}6、测试类
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")publicclassTestAccount{@AutowiredprivateAccountServiceaccountService;@TestpublicvoidtestTransfer(){accountService.transfer("Jack","Rose",500);}}结论
如果没有事务控制,转账业务出现异常,会出现数据库中数据不正确的情况
知识点
需要导入tx命名空间
功能:指定所有业务层方法的事务使用规则,包含了下面的子元素
功能:指定某个方法事务操作规则
事务通知的配置
事务是切面类,要织入的对象
AOP的配置
只需要修改配置文件
执行成功
注意点
1、配置
2、配置transfer方法,指定事务的传播行为REQUIRED,不是只读事务,如果是查询的方法,配置传播行为SUPPORTS,只读事务,查询不需要修改。
@Transactional
用于注解式事务
注解的应用范围
注解的参数和作用
@ServicepublicclassAccountServiceImplimplementsAccountService{@AutowiredprivateAccountDaoaccountDao;/***转账的操作*@paramfromUser转出账户*@paramtoUser转入账户*@parammoney多少钱*/@Override@Transactional(rollbackFor=Exception.class)publicvoidtransfer(StringfromUser,StringtoUser,doublemoney)throwsSQLException{//从一个账户扣钱accountDao.updateAccount(fromUser,-money);//模拟出错的情况//System.out.println(1/0);if(money==100){//不是运行时异常,默认不会回滚thrownewSQLException("不允许的金额");}//另一个账户加钱accountDao.updateAccount(toUser,money);}}结论:当配置rollbackFor=RuntimeException.class时,出现/byzero异常会回滚,出现SQLException不会回滚,数据异常。只有配置rollbackFor=Exception.class才会都回滚。
分析
所有配置文件都要删除,使用相应的注解代替,applicationContext.xml配置如下:
@EnableTransactionManagement
相当于:
/***配置数据库*/@PropertySource("druid.properties")publicclassJdbcConfig{@Value("${jdbc.username}")privateStringusername;@Value("${jdbc.password}")privateStringpassword;@Value("${jdbc.url}")privateStringurl;@Value("${jdbc.driverClassName}")privateStringdriverClassName;//创建数据源,返回值放在容器中@BeanpublicDataSourcecreateDataSource(){DruidDataSourceds=newDruidDataSource();ds.setUsername(username);ds.setPassword(password);ds.setUrl(url);ds.setDriverClassName(driverClassName);returnds;}//自动从容器中按类型匹配的方式去查找@BeanpublicJdbcTemplatecreateJdbcTemplate(DataSourcedataSource){returnnewJdbcTemplate(dataSource);}//创建事务管理器@BeanpublicTransactionManagercreateTransactionManager(DataSourcedataSource){returnnewDataSourceTransactionManager(dataSource);}}3、SpringConfiguration中导入JdbcConfig
@Configuration//这是配置类@ComponentScan("com.itheima")//扫描基包@Import(JdbcConfig.class)//导入另一个配置类@EnableTransactionManagement//开启事务管理器publicclassSpringConfiguration{}