目录
4.3、第一个IOC示例程序 -- 通过id获取对象(重点)
4.6、IOC示例程序 -- index属性指定构造器参数的顺序
5.2、Bean的后置处理器BeanPostProcessor
6.2、Spring引入单独的jdbc.properties配置文件(重点)
6.3、使用context名称空间加载jdbc.properties配置文件(重点)
8.1、使用注解配置Dao、Service、Controller组件
8.7、@Autowired和@Qualifier在方法上的使用。
实验2:将id=5的记录的salary字段更新为1300.00
实验4:查询id=5的数据库记录,封装为一个Java对象返回
实验5:查询salary>4000的数据库记录,封装为List集合返回
实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值
实验9:通过继承JdbcDaoSupport创建JdbcTemplate的Dao
Spring事务引入的分析------PlatformTransactionManager类简单介绍
11.5、noRollbackFor和noRollbackForClassName测试不回滚的异常
实验3:大事务是REQUIRED,小1REQUIRED,小2REQUIRES_NEW
获取WebApplicationContext上下文对象的方法如下:
Spring-01
1、Spring概述
①Spring是一个开源框架
②Spring为简化企业级开发而生,使用Spring开发可以将Bean对象,Dao组件对象,Service组件对象等交给Spring容器来管理,这样使得很多复杂的代码在Spring中开发却变得非常的优雅和简洁,有效的降低代码的耦合度,极大的方便项目的后期维护、升级和扩展。
③Spring是一个IOC(DI)和AOP容器框架。
④Spring的优良特性
[1]非侵入式:基于Spring开发的应用中的对象可以不依赖于Spring的API
[2]控制反转:IOC——Inversion of Control,指的是将对象的创建权交给Spring去创建。使用Spring之前,对象的创建都是由我们自己在代码中new创建。而使用Spring之后。对象的创建都是由给了Spring框架。
[3]依赖注入:DI——Dependency Injection,是指依赖的对象不需要手动调用setXX方法去设置,而是通过配置赋值。
[4]面向切面编程:Aspect Oriented Programming——AOP面向切面编程
[5]容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
[6]组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用XML和Java注解组合这些对象。
[7]一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring 自身也提供了表述层的SpringMVC和持久层的Spring JDBC)。
2、Spring的模块介绍
Spring框架分为四大模块:
Core核心模块。负责管理组件的Bean对象
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
面向切面编程
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
数据库操作
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-oxm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
spring-jms-4.0.0.RELEASE.jar
Web模块
spring-web-4.0.0.RELEASE.jar
spring-webmvc-4.0.0.RELEASE.jar
spring-websocket-4.0.0.RELEASE.jar
spring-webmvc-portlet-4.0.0.RELEASE.jar
4、IOC控制反转
4.1、什么是IOC
IOC 全称指的是 Inverse Of Control 控制反转。
在使用Spring框架之前,我们的对象,都是通过代码我们自己去new创建对象实例.
在使用了Spring框架之后 , 对象的创建是由给spring容器来负责.
注意 : 如果自己去new对象 . 就不能使用Spring的功能.
4.2、什么是DI
DI 指的是Dependency Injection 。是依赖注入的意思。
依赖是指需要依靠的对象.
注入是指给依靠的对象赋值.
DI依赖注入,就是给子对象赋值.
比如:
public class BookService{
public bookDao bookDao;
public void setBookDao( BookDao bookDao ) {
this.bookDao = bookDao;
}
}
而使用了Spring之后.我们对依赖对象的赋值只需要通过xml配置或者注解配置即可!!!!
4.3、第一个IOC示例程序 -- 通过id获取对象(重点)
实验1:通过IOC容器创建对象,并为属性赋值★
创建一个Java模块:
2 导入需要的jar包:
junit_4.12.jar
org.hamcrest.core_1.3.0.jar
spring-beans-5.2.5.RELEASE.jar
spring-context-5.2.5.RELEASE.jar
spring-core-5.2.5.RELEASE.jar
spring-expression-5.2.5.RELEASE.jar
spring-jcl-5.2.5.RELEASE.jar
3 创建Person类:
public class Person { private Integer id; private String name; private String phone; private Integer age;
4 在src源码目录下编写applicationContext.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!-- bean标签表示配置一个Bean对象实例 class属性表示对象的具体全类名 id属性设置唯一标识 --> <bean class="com.demo.pojo.Person" id="p1"> <!-- property标签是通过setXxx方法对属性赋值操作 name 是属性名 value是属性值 --> <property name="id" value="1"/> <property name="name" value="张三"/> <property name="phone" value="18610541354"/> <property name="age" value="18"/> </bean> </beans>
编写测试代码:
@Test public void test1() { // 在使用Spring框架的时候,一定要先获取Spring容器对象( Spring IOC 容器对象 ) // 在Spring中,容器对象由接口ApplicationContext表示 // ClassPathXmlApplicationContext类表示从ClassPath类路径下加载xml配置文件创建Spring容器对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); // getBean()从容器中获取Bean对象 Person person = (Person) applicationContext.getBean("p1"); System.out.println(person); }
问题:
1、FileSystemXmlApplicationContext怎么用?
答:跟使用JavaSE的相对路径一样
ApplicationContext applicationContext = new FileSystemXmlApplicationContext("src/applicationContext.xml");
2、Bean是在什么时候被创建的?
答:在创建ApplicatiocnContext容器对象时一起创建Bean对象(默认)
- 如果调用getBean多次,会创建几个?
答:默认创建同一个
常见的错误:
指定的id不存在。找不到bean对象。
4.4、IOC示例程序 -- 通过类型获取对象(重点)
实验2:根据bean的类型从IOC容器中获取bean的实例★
@Test public void test2() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); /** * 通过具体的类型获取 <br/> * 1 如果通过class类型找到唯一一个,就返回 <br/> * 2 如果没有找到就报错<br/> * 3 如果通过class类型找到多个,也报错<br/> */ Person bean = applicationContext.getBean(Person.class); System.out.println(bean); }
applicationContext.xml配置文件:
<!-- bean标签表示配置一个Bean对象实例 class属性表示对象的具体全类名 id属性设置唯一标识 --> <bean class="com.demo.pojo.Person" id="p1"> <!-- property标签是通过setXxx方法对属性赋值操作 name 是属性名 value是属性值 --> <property name="id" value="1"/> <property name="name" value="张三"/> <property name="phone" value="18610541354"/> <property name="age" value="18"/> </bean> <bean class="com.demo.pojo.Person" id="p2"> </bean>
常见错误说明:
当在applicationContext.xml配置文件中。有多个同Person.class类型实现的时候。
4.5、IOC示例程序 -- 通过构造方法参数名注入值
实验3:通过构造器为bean的属性赋值
配置内容:
<bean class="com.demo.pojo.Person" id="p3"> <!--public Person(Integer id, String name, String phone, Integer age) constructor-arg是通过构造器参数进行赋值属性 name 是参数名 value是参数值 --> <constructor-arg name="id" value="3" /> <constructor-arg name="name" value="有参构造器,参数名对应赋值" /> <constructor-arg name="phone" value="电话" /> <constructor-arg name="age" value="13" /> </bean>
测试代码:
@Test public void test3() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(applicationContext.getBean("p3")); }
4.6、IOC示例程序 -- index属性指定构造器参数的顺序
实验4:通过index属性指定参数的位置
配置文件:
<bean class="com.demo.pojo.Person" id="p4"> <!--public Person(Integer id, String name, String phone, Integer age) index表示参数的索引顺序,从零开始 value表示参数值 --> <constructor-arg index="0" value="4" /> <constructor-arg index="1" value="我是name属性" /> <constructor-arg index="2" value="我是phone电话" /> <constructor-arg index="3" value="18" /> </bean>
测试代码:
@Test public void test4() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(applicationContext.getBean("p4")); }
4.7、IOC示例程序 -- 根据参数类型注入
实验5:根据参数类型注入
添加多个有参构造器:
public Person(Integer id, String name, String phone, Integer age) { System.out.println("有参 Person 被创建了....."); this.id = id; this.name = name; this.phone = phone; this.age = age; } public Person(Integer id, String name, Integer age,String phone) { System.out.println("有参 Person 被创建了....."); this.id = id; this.name = name; this.phone = phone; this.age = age; }
配置文件内容:
<bean class="com.demo.pojo.Person" id="p5"> <!-- 构造器1 Person(Integer id, String name, String phone, Integer age) 构造器2 Person(Integer id, String name, Integer age,String phone) index 是参数索引 value 是参数值 type是参数类型 --> <constructor-arg index="0" value="5" type="java.lang.Integer" /> <constructor-arg index="1" value="类型赋值" type="java.lang.String" /> <constructor-arg index="2" value="110" type="java.lang.String"/> <constructor-arg index="3" value="120" type="java.lang.Integer" /> </bean>
测试代码:
@Test public void test5() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(applicationContext.getBean("p5")); }
4.8、IOC之 P名称空间
p名称空间,可以以非常简短的形式通过调用setXxx方法给属性赋值.
实验6:通过p名称空间为bean赋值
配置信息如下:
<!-- p名称空间的使用格式如下: p:属性名="值" --> <bean class="com.demo.pojo.Person" id="p6" p:id="6" p:name="p名称空间赋值" p:age="18" p:phone="电话" />
测试代码:
@Test public void test6() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(applicationContext.getBean("p6")); }
4.9、测试null值的使用
实验7:测试使用null值
配置信息:
<bean class="com.demo.pojo.Person" id="p7"> <property name="id" value="7" /> <!-- 我希望赋于null空值 --> <property name="name" > <!-- null标签表示null值 --> <null></null> </property> </bean>
测试代码:
@Test public void test7() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person = (Person) applicationContext.getBean("p7"); System.out.println(person.getName()); System.out.println(person.getName().length()); }
4.10、IOC之子对象的赋值测试(重点)
实验8:引用其他bean★
创建个新的工程。测试Spring的开发环境。此不重复。请参阅前面,环境搭建。
添加如下的类:
public class Car { private String name; private String carNo;
public class Person { private Integer id; private String name; private String phone; private Integer age; private Car car;
配置文件内容:
<bean class="com.demo.pojo.Car" id="car"> <property name="name" value="托垃圾" /> <property name="carNo" value="京B66666" /> </bean> <bean class="com.demo.pojo.Person" id="p8"> <property name="id" value="8" /> <property name="name" value="子对象赋值" /> <!-- ref表示引用 --> <property name="car" ref="car" /> </bean>
测试代码:
@Test public void test8() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person = (Person) applicationContext.getBean("p8"); System.out.println(person); }
4.11、IOC之内部Bean的使用
内部Bean指的是在bean标签内定义的bean对象.它不能被 spring 容器直接获取.
实验9:引用内部bean
配置信息:
<bean class="com.demo.pojo.Person" id="p9"> <property name="id" value="9" /> <property name="name" value="内部Bean" /> <property name="car" > <!-- 内部Bean --> <bean class="com.demo.pojo.Car" id="car2"> <property name="name" value="饱马"/> <property name="carNo" value="京A111111" /> </bean> </property> </bean>
测试代码:
@Test public void test9() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Person person = (Person) applicationContext.getBean("p9"); System.out.println(person); System.out.println( applicationContext.getBean("car") ); System.out.println( applicationContext.getBean("car2") ); }
常见错误:内部的Bean不能被外部使用
4.12、IOC之List属性的赋值
实验10:使用list子元素为List类型的属性赋值
类添加list集合属性:
public class Person { private Integer id; private String name; private String phone; private Integer age; private Car car; private List<String> list; public List<String> getList() { return list; } public void setList(List<String> list) { this.list = list; }
配置文件内容:
<bean class="com.demo.pojo.Person" id="p10"> <property name="id" value="10"/> <property name="list"> <!-- list标签表示赋值的元素是list集合 --> <list> <value>item1</value> <value>item2</value> <value>item3</value> </list> </property> </bean>
测试的代码:
@Test public void test10() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p10"); System.out.println(person); }
4.13、IOC之Map属性的赋值
实验11:使用map子元素为Map类型的属性赋值
给person添加map类型的属性:
public class Person { private Integer id; private String name; private String phone; private Integer age; private Car car; private List<String> list; private Map<String,Object> map; public void setMap(Map<String, Object> map) { this.map = map; } public Map<String, Object> getMap() { return map; }
配置文件内容
<bean class="com.demo.pojo.Person" id="p11"> <property name="id" value="11" /> <property name="map"> <!-- map标签表示赋值的类型的map集合 --> <map> <!-- 表示每一个键值对 --> <entry key="key1" value="value1" /> <entry key="key2" value="value2" /> <entry key="key3" value="value3" /> </map> </property> </bean>
测试的代码:
@Test public void test11() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p11"); System.out.println(person); }
4.14、IOC之Properties属性的赋值
实验12:使用prop子元素为Properties类型的属性赋值
给Person添加属性:
public class Person { private Integer id; private String name; private String phone; private Integer age; private Car car; private List<String> list; private Map<String,Object> map; private Properties props; public void setProps(Properties props) { this.props = props; } public Properties getProps() { return props; }
配置文件内容:
<bean class="com.demo.pojo.Person" id="p12"> <property name="id" value="12" /> <property name="props"> <!-- props标签表示赋值的类型是Properties类型 --> <props> <!-- prop表示一个键值对 --> <prop key="url">jdbc:mysql://localhost:3306/test</prop> <prop key="username">root</prop> <prop key="driverClassName">com.mysql.jdbc.Driver</prop> </props> </property> </bean>
测试代码:
@Test public void test12() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p12"); System.out.println(person); }
4.15、IOC之util 名称空间
util名称空间,可以定义全局公共的集合信息,方便容器直接获取,或者是给属性赋值使用.
实验13:通过util名称空间创建集合类型的bean
添加util名称空间:
配置信息:
<!-- 可以从容器中直接获取到 也可以给list集合属性赋值使用 --> <util:list id="list01"> <value>这是第1妹子</value> <value>这是第2妹子</value> <value>这是第3妹子</value> </util:list> <bean class="com.demo.pojo.Person" id="p13"> <property name="id" value="13" /> <property name="list" ref="list01" /> </bean>
测试代码:
@Test public void test13() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p13"); System.out.println(person); System.out.println(applicationContext.getBean("list01")); }
4.16、IOC之级联属性赋值
实验14:给bean的级联属性赋值
配置文件内容:
<bean class="com.demo.pojo.Person" id="p14"> <property name="id" value="14" /> <property name="car"> <bean class="com.demo.pojo.Car" id="car"> <property name="name" value="笨池" /> <property name="carNo" value="京C333333" /> </bean> </property> <!-- 在spring中,如果要对属性使用级联属性赋值.需要先给car属性赋值 --> <property name="car.name" value="级联属性赋值" /> </bean>
测试的代码:
@Test public void test14() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p14"); System.out.println(person); }
注意:级联属性一定要先注入对象。再注入对象的属性
4.17、IOC之*静态工厂方法*创建Bean
实验15:配置通过静态工厂方法创建的bean
工厂类:
public class PersonFactory { public static Person createPerson(){ return new Person(15,"静态工厂方法","18610541354", 18); } }
配置内容:
<!-- 静态工厂方法使用class属性和factory-method属性组合使用 class表示工厂的全类名 factory-method属性静态方法名 --> <bean id="p15" class="com.demo.factory.PersonFactory" factory-method="createPerson"/>
测试代码:
@Test public void test15() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p15"); System.out.println(person); }
4.18、IOC之*工厂实例方法*创建Bean
实验16:配置通过实例工厂方法创建的bean
工厂实例代码:
public class PersonFactory {
public Person createPerson2(){ return new Person(16,"工厂实例方法","张三", 18); } }
配置信息
<!-- 工厂实例方法创建Bean对象,需要由 bean + factory-bean + factory-method组合实现 --> <bean class="com.demo.factory.PersonFactory" id="personFactory"/> <!-- factory-bean 工厂实例对象 factory-method 工厂方法名 --> <bean id="p16" factory-bean="personFactory" factory-method="createPerson2" />
测试代码:
@Test public void test16() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p16"); System.out.println(person);
4.19、IOC之FactoryBean接口方式创建对象
实验17:配置FactoryBean接口创建Bean对象
1 创建一个类去实现FactoryBean接口
2 实现它的方法
3 到Spring的配置文件中去配置
FactroryBean接口实现类:
public class PersonFactoryBean implements FactoryBean<Person> { /** * 创建Bean对象时创建的方法 * @return * @throws Exception */ @Override public Person getObject() throws Exception { return new Person(17,"FactoryBean接口方式","120",18); } /** * 获取对象的Class类型的方法 * @return */ @Override public Class<?> getObjectType() { return Person.class; /** * 是否是单例 * @return */ @Override public boolean isSingleton() { return false; } }
配置文件:
<!-- FactoryBean接口的方式创建的对象 --> <bean id="p17" class="com.demo.factory.PersonFactoryBean" />
测试的代码:
@Test public void test17() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean1.xml"); Person person = (Person) applicationContext.getBean("p17"); System.out.println(person); }
4.20、IOC之继承Bean配置
实验18:通过继承实现bean配置信息的重用
配置文件
<bean id="parent" class="com.demo.pojo.Person"> <property name="id" value="100" /> <property name="name" value="父配置" /> <property name="age" value="100" /> <property name="phone" value="110" /> </bean> <!-- parent属性设置继承哪个Bean的配置 --> <bean class="com.demo.pojo.Person" id="p18" parent="parent"> <property name="id" value="18" /> <property name="phone" value="18610541354" /> </bean>
测试代码:
@Test public void test18() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean2.xml"); Person person = (Person) applicationContext.getBean("p18"); System.out.println(person); }
4.21、IOC之abstract抽象Bean
实验19:通过abstract属性创建一个模板bean
<!-- abstract="true" 表示当前配置信息,只能用于继承,不能被实例化 --> <bean id="parent" class="com.demo.pojo.Person" abstract="true"> <property name="id" value="100" /> <property name="name" value="父配置" /> <property name="age" value="100" /> <property name="phone" value="110" /> </bean>
4.22、IOC之组件( Bean对象 )创建顺序
实验20:bean之间的依赖 depends-on 属性
在Spring容器中.Bean对象的创建顺序默认是他们在配置文件中,从上到下的顺序决定.
public class A { public A() {System.out.println("A 被创建了");} }
public class B { public B() {System.out.println("B 被创建了");} }
public class C { public C() {System.out.println("C 被创建了");} }
配置信息:
<!-- 1 在Spring容器中.Bean对象的创建顺序默认是他们在配置文件中,从上到下的顺序决定. 2 可以在bean的配置上,使用属性depends-on表示前置创建 --> <bean class="com.demo.pojo.A" id="a" depends-on="b,c"/> <bean class="com.demo.pojo.B" id="b" /> <bean class="com.demo.pojo.C" id="c" />
测试代码:
@Test public void test19() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean3.xml"); }
4.23、IOC之Bean的单例和多例(重点)
实验21:测试bean的作用域,分别创建单实例和多实例的bean★
<!-- scope表示配置 Bean的作用域 singleton 表示单例 (默认值) 1 会跟着Spring容器一起被创建 2 多次调用getBean()方法都返回同一个对象 prototype 表示多例 1 不会跟着Spring容器一起被创建 2 每次调用getBean() 方法都会返回一个新创建的对象 request 表示一次请求内多次调用getBean都返回同一个对象 session 表示一个会话内多次调用getBean都返回同一个对象 --> <bean class="com.demo.pojo.Person" id="p20" scope="singleton"> <property name="id" value="20" /> </bean>
测试的代码:
@Test public void test19() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean3.xml"); System.out.println( applicationContext.getBean("p20") ); System.out.println( applicationContext.getBean("p20") ); System.out.println( applicationContext.getBean("p20") ); System.out.println( applicationContext.getBean("p20") ); }
4.24、基于xml配置文件的自动注入
先创建Person类和Car类
public class Car {
private String name;
public class Person {
private Car car;
public Person(Car car) {
this.car = car;
}
配置文件内容:
<bean class="com.demo.pojo.Car" id="car3"> <property name="name" value="劳洗来洗" /> <property name="carNo" value="京B22222" /> </bean> <bean class="com.demo.pojo.Car" id="car"> <property name="name" value="露糊" /> <property name="carNo" value="京B33333" /> </bean> <!-- 自动注入 指的是 按照某种指定的算法,自动的给子对象赋值 autowire属性设置一种自动注入的算法 default 和 no 都表示不自动赋值子对象 byName 表示按子对象的属性名做为id到spring容器中查找并注入 1 如果找到就赋值 2 如果没有找到,就null空值 byType 表示按子对象的类型到Spring容器中查找并注入 1 按类型如果找到一个就注入 2 如果没有找到,就null空值 3 如果找到多个就报错 constructor 表示按构造器参数进行查找并注入 1 先按类型查找到一个,找到就赋值 2 如果按类型找到多个,接着按参数名做为id继续查找并注入 3 如果按类型找到多个,再按参数名做id没有找到,就不赋值 4 如果按类型找不到,也不赋值 --> <bean class="com.demo.pojo.Person" id="p21" autowire="constructor"> <property name="id" value="21" /> </bean>
测试的代码:
@Test public void test21() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean3.xml"); System.out.println( ap===
public class Person { private Integer id; private String name; priva===te String phone; private Integer age; private Car car; /*初始化方法 */ public void init(){ System.out.println(" init() 这里可以做一些初始化操作 "); } /* 销毁方法 */ public void destroy(){ System.out.println(" destroy() 这里可以做一些销毁操作 "); }
配置内容:
<!-- init-method 是初始化方法 ( 在Bean对象创建之后马上调用 ) destroy-method 是销毁方法 ( 在Spring容器关闭的时候调用 , 只对单例有效 ) --> <bean class="com.demo.pojo.Person" id="p22" scope="prototype" init-method="init" destroy-method="destroy"></bean>
测试的代码:
@Test public void test22() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean3.xml"); System.out.println( applicationContext.getBean("p22") ); applicationContext.close();//关闭容器,释放资源 }
5.2、Bean的后置处理器BeanPostProcessor
1 Bean的后置处理器可以给bean对象初始化方法前后调用,做一些操作
2 使用步骤如下:
2.1 编写一个类去实现BeanPostProcessor接口
2.2 实现接口的两个方法
2.3 到Spring的配置文件中去配置后置处理器
实验23:测试bean的后置处理器
后置处理器代码 :
publuc class MyBeanPostProcessor implements BeanPostProcessor { /** * 在初始化方法之前执行,做一些操作<br/> * @param bean 当前初始化的对象实例 * @param beanName 当前初始化对象的id值 * @return 返回值是当前初始化对象( 它会替代当前初始化对象 ) */ @Override public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { System.out.println( " 初始化之前 obj => " + bean + " , id =>" + beanName ); return bean; } /** * 在初始化方法之后执行,做一些操作<br/> * @param bean 当前初始化的对象实例 * @param beanName 当前初始化对象的id值 */ @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println( " 初始化之后 obj => " + bean + " , id =>" + beanName ); if ("p22".equals(beanName)) { Person p = (Person) bean; p.setCar(new Car("QQ卡丁车", "京C444444")); } return bean; } }
配置信息
<!-- init-method 是初始化方法 ( 在Bean对象创建之后马上调用 ) destroy-method 是销毁方法 ( 在Spring容器关闭的时候调用 , 只对单例有效 ) --> <bean class="com.demo.pojo.Person" id="p22" scope="prototype" init-method="init" destroy-method="destroy"></bean> <!-- 配置后置处理器 --> <bean class="com.demo.processor.MyBeanPostProcessor" />
测试代码:
@Test public void test22() { ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean3.xml"); System.out.println( applicationContext.getBean("p22") ); applicationContext.close();//关闭容器,释放资源 }
Spring-02
- Spring管理数据库连接池(重点)
6.1、Spring配置管理数据库连接池对象(重点)配置文件信息:
<!-- 配置了数据库连接池 --> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="username" value="root" /> <property name="password" value="root" /> <property name="url" value="jdbc:mysql://localhost:3306/book" /> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="initialSize" value="5" /> <property name="maxActive" value="10" /> </bean>
测试代码:
@Test public void test1() throws SQLException { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
username=root password=root url=jdbc:mysql://localhost:3306/book driverClassName=com.mysql.jdbc.Driver initialSize=5 maxActive=10
Spring配置文件:
<!-- 它可以加载指定的属性配置文件 --> <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <!-- location属性设置加载 的属性配置文件路径 classpath:jdbc.properties表示路径 classpath: 表示从类路径下查找 jdbc.properties 表示文件名 classpath:jdbc.properties 表示从类路径下加载jdbc.poperties属性配置文件 --> <property name="location" value="classpath:jdbc.properties" /> </bean> <!-- 配置了数据库连接池 --> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="username" value="${username}" /> <property name="password" value="${password}" /> <property name="url" value="${url}" /> <property name="driverClassName" value="${driverClassName}" /> <property name="initialSize" value="${initialSize}" /> <property name="maxActive" value="${maxActive}" /> </bean>
测试代码:
@Test public void test1() throws SQLException { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) applicationContext.getBean("dataSource"); System.out.println(dataSource.getConnection()); }
6.3、使用context名称空间加载jdbc.properties配置文件(重点)
jdbc.properteis属性配置文件:
user=root password=root url=jdbc:mysql://localhost:3306/book driverClassName=com.mysql.jdbc.Driver initialSize=5 maxActive=10
Spring配置文件:
<!-- 使用context名称空间加载属性配置文件 location属性表示加载的文件的路径 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 配置了数据库连接池 --> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="username" value="${user}" /> <property name="password" value="${password}" /> <property name="url" value="${url}" /> <property name="driverClassName" value="${driverClassName}" /> <property name="initialSize" value="${initialSize}" /> <property name="maxActive" value="${maxActive}" /> </bean>
测试代码:
@Test public void test1() throws SQLException { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); DataSource dataSource = (DataSource) applicationContext.getBean("dataSource"); System.out.println(dataSource.getConnection()); }
- Spring EL表达式(了解内容)
创建java实体Bean对象
public class Person {
private int id;
private String name;
private String phone;
private double salary;
private Car car;
public class Car {
private String name;
private String carNo;
实验26:[SpEL测试I]在SpEL中使用字面量
使用格式:#{数值} #{“字符串” || ‘字符串’}
实验27:[SpEL测试II]在SpEL中引用其他
使用格式: #{bean.方法名(参数)}
实验30:[SpEL测试V]在SpEL中调用静态方法
使用格式:#{T(全名类).方法名(参数)}
实验31:[SpEL测试VI]在SpEL中使用运算符
使用格式:#{表达式}
JavaBean类:
public class Car { private String carNo; private String name; public String noStaticFun(){ return "非静态方法"; } public static String staticFun(){ return "静态方法"; }
public class Person { private Integer id; private String name; private String phone; private Double salary; private Car car;
Spring配置文件:
<bean class="com.demo.pojo.Car" id="car" > <property name="name" value="卖爸喝" /> <property name="carNo" value="京B666666" /> </bean> <bean class="com.demo.pojo.Person" id="pel"> <!-- Spring EL表达式 常量用法 --> <property name="id" value="#{100}" /> <!--<property name="name" value="#{'SpringEL的字符串赋值'}" />--> <!-- Spring EL 表达式 引用其他Bean对象 --> <property name="car" value="#{car}" /> <!-- Spring EL表达式,引用其他Bean的属性值 --> <property name="phone" value="#{car.name}" /> <!-- Spring EL表达式 调用非静态方法 --> <!--<property name="name" value="#{car.noStaticFun()}" />--> <!-- Spring EL表达式 调用静态方法 --> <property name="name" value="#{T(com.demo.pojo.Car).staticFun()}" /> <!-- Spring EL表达式 做运算操作 --> <property name="salary" value="#{30000*12}" /> </bean>
测试代码:
@Test public void test2() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println( applicationContext.getBean("pel") ); }
- 注解功能 (重点)
8.1、使用注解配置Dao、Service、Controller组件
实验32:通过注解分别创建Dao、Service、Controller★
Spring配置bean的常用注解有
@Controller 配置Web层的组件.
@Service 配置Service层的组件
@Repository 配置DAO组件
@Component 配置Web层,Servcie层,DAO层之外的Bean对象.使用@Component
@Scope 配置Bean的作用域 (单例.多例)
Bean对象:
/** * @Component注解的作用是:<br/> * 相当于以下的配置: <bean class="com.demo.pojo.Book" id="book" /> */ //@Scope("prototype")//表示多例 @Scope("singleton")//表示单例 @Component public class Book { }
/** * @Repository 注解的作用相当于: <bean class="com.demo.dao.BookDao" id="bookDao" /> */ @Repository public class BookDao {}
/** * @Service注解的作用相当于: <bean class="com.demo.service.BookService" id="bookService" /> */ @Service public class BookService {}
/** * @Controller注解的作用是: <bean class="com.demo.controller.BookController" id="bookController" /> */ @Controller public class BookController {}
Spring配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置包扫描( 包扫描就是扫描类,然后去找哪些类配置了注解 ) base-package 指定扫描哪些包下的类( 包含子包 ) --> <context:component-scan base-package="com.demo" />
</beans>
测试代码:
@Test public void test1() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); System.out.println(applicationContext.getBean("book")); System.out.println(applicationContext.getBean("bookDao")); System.out.println(applicationContext.getBean("bookService")); System.out.println(applicationContext.getBean("bookController")); }
8.2、指定扫描包时的过滤内容
实验33:使用context:include-filter指定扫描包时要包含的类
实验34:使用context:exclude-filter指定扫描包时不包含的类
<context:include-filter /> 设置包含的内容
注意:通常需要与use-default-filters属性配合使用才能够达到“仅包含某些组件”这样的效果。即:通过将use-default-filters属性设置为false,
<context:exclude-filter /> 设置排除的内容
类别 | 示例 | 说明 |
annotation | com.demo.XxxAnnotation | 过滤所有标注了XxxAnnotation的类。这个规则根据目标组件是否标注了指定类型的注解进行过滤 |
assignable | com.demo.BaseXxx | 过滤所有BaseXxx类的子类。这个规则根据目标组件是否是指定类型的子类的方式进行过滤。 |
aspectj | com.demo.*Service+ | 所有类名是以Service结束的,或这样的类的子类。这个规则根据AspectJ表达式进行过滤。 |
regex | com\.demo\.anno\.* | 所有com.demo.anno包下的类。这个规则根据正则表达式匹配到的类名进行过滤。 |
custom | com.demo.XxxTypeFilter | 使用XxxTypeFilter类通过编码的方式自定义过滤规则。该类必须实现org.springframework.core.type.filter.TypeFilter接口 |
自定义排除示例:
<!-- 配置包扫描( 包扫描就是扫描类,然后去找哪些类配置了注解 ) base-package 指定扫描哪些包下的类( 包含子包 ) --> <context:component-scan base-package="com.demo" > <!-- context:exclude-filter自定义排除(不让它在Spring容器中) type表示使用哪种类型的算法进行过滤 type="annotation"表示按注解 expression跟指定算法需要的表达式,(就是注解全类名) type="assignable" 表示按类型来进行过滤 --> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> <context:exclude-filter type="assignable" expression="com.demo.service.BookService"/> </context:component-scan>
自定义包含示例:
<!-- 配置包扫描( 包扫描就是扫描类,然后去找哪些类配置了注解 ) base-package 指定扫描哪些包下的类( 包含子包 ) --> <context:component-scan base-package="com.demo" use-default-filters="false"> <!-- context:include-filter标签是自定义包含(必须要和use-default-filters="false"一起组合使用) type属性指定使用哪种算法过滤 expression属性指定需要的表达式 --> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> <!-- assignable算法,也会包含子类 --> <context:include-filter type="assignable" expression="com.demo.service.BookService"/> </context:component-scan>
注:
1 @Controller @Service 和 @Repository 都继承了
@Autowired 注解 会自动的根据标注的对象类型在Spring容器中查找相对应的类。如果找到,就自动装配。
使用@Autowired注解,不需要get/set方法
@Service public class BookService { /** * @Autowired 注解的作用是到Spring容器中查找BookDao并赋值<br/> * 1 先按类型查找,并注入<br/> */ @Autowired private BookDao bookDao; @Override public String toString() { return "BookService{" + "bookDao=" + bookDao + '}'; } }
8.4、多个同类型的bean如何自动装配
实验36:如果资源类型的bean不止一个,默认根据@Autowired注解标记的成员变量名作为id查找bean,进行装配★
/** * @Repository 注解的作用相当于: <bean class="com.demo.dao.BookDao" id="bookDao" /> */ @Repository public class BookDao {}
@Repository public class BookDaoExt extends BookDao {}
@Service public class BookService { /** * @Autowired 注解的作用是到Spring容器中查找BookDao并赋值<br/> * 1 先按类型查找,并注入<br/> * 2 如果按类型查找到多个,接着按变量名做为id到Spring容器中继续查找并注入<br/> */ @Autowired private BookDao bookDaoExt; @Override public String toString() { return "BookService{" + "bookDao=" + bookDaoExt + '}'; } }
8.5、使用@Qualifier装配指定id的bean对象
实验37:如果根据成员变量名作为id还是找不到bean,可以使用@Qualifier注解明确指定目标bean的id★
@Service public class BookService { /** * @Autowired 注解的作用是到Spring容器中查找BookDao并赋值<br/> * 1 先按类型查找,并注入<br/> * 2 如果按类型查找到多个,接着按变量名做为id到Spring容器中继续查找并注入<br/> * 3 可以使用@Qualifier注解指定一个id到容器中查找并注入(属性名就会忽略)<br/> */ @Qualifier("bookDaoExt") @Autowired private BookDao bookDao; @Override public String toString() { return "BookService{" + "bookDao=" + bookDao + '}'; } }
8.6、@Autowired注解的required属性作用
实验39:@Autowired注解的required属性指定某个属性允许不被设置
@Service public class BookService { /** * @Autowired 注解的作用是到Spring容器中查找BookDao并赋值<br/> * 1 先按类型查找,并注入<br/> * 2 如果按类型查找到多个,接着按变量名做为id到Spring容器中继续查找并注入<br/> * 3 可以使用@Qualifier注解指定一个id到容器中查找并注入(属性名就会忽略)<br/> * 4 如果@Qualifier注解指定id找不到,可以设置@Autowired(required = false)允许值为null. */ @Qualifier("abc") @Autowired(required = false) private BookDao bookDao; @Override public String toString() { return "BookService{" + "bookDao=" + bookDao + '}'; } }
8.7、@Autowired和@Qualifier在方法上的使用。
实验38:在方法的形参位置使用@Qualifier注解
/** *@Autowired 注解如果被标注在方法上,对象一旦实例化,就会马上调用.可以做一些初始化操作<br/> * 1 先按照参数的类型到Spring容器中查找,并调用方法<br/> * 2 如果按类型找到多个,再按参数名做为id继续到Spring容器中查找并调用方法<br/> * 3 可以使用@Qualifier注解指定一个id到容器中查找并调用方法(属性名就会忽略)<br/> * 4 如果@Qualifier注解指定id找不到,可以设置@Autowired(required = false)允许值为null.(不调用方法) */ @Autowired(required = false) public void abc(@Qualifier("boo") BookDao bookDaoExt1){ System.out.println(" abc() --->>> " + bookDaoExt1); this.bookDao = bookDaoExt1; }
- Spring的专有测试
@ContextConfiguration
@RunWith
Spring为了让Junit测试变得更佳简单,写的测试代码更少.
专门为Junit做了一些扩展操作.
1 自己实现一个Junit4 的运行器类
2 在扩展的Junit4 的类中,有一个Spring容器,不再需要我们自己去实现这个容器.
3 使用Spring提供的扩展的Junit测试 , 还可以使用 Spring的依赖注入功能.
import com.demo.pojo.Book; import com.demo.pojo.User; import com.demo.service.BookService; import com.demo.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; /** * Spring扩展的Junit测试里有Spring容器<br/> */ // @ContextConfiguration注解的作用是指定Spring容器需要的配置文件路径 @ContextConfiguration(locations = "classpath:applicationContext.xml") // @RunWith表示使用Spring扩展的Junit测试类来测试代码 @RunWith(SpringJUnit4ClassRunner.class) public class SpringJunitTest { @Autowired BookService bookService; @Autowired UserService userService; @Test public void test() { bookService.saveEntity(new Book()); System.out.println("========================"); userService.saveEntity(new User()); } }
9、AOP切面编程
9.1、什么是AOP
AOP是面向切面编程。全称:Aspect Oriented Programming
面向切面编程指的是:程序是运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。
9.2、一个简单计算数功能加日记
计算器接口
public interface Calculate { public int add(int num1, int num2); public int add(int num1, int num2, int num3); public int div(int num1, int num2); }
日记工具类:
public class LogUtils { public static void logBefore(String method,Object ... args){ System.out.println(" 当前运算是 " + method + " , 参数是: " + Arrays.asList(args)); } public static void logAfterReturning(String method,Object result){ System.out.println(" 当前运算是 " + method + " , 结果是: " + result); } public static void logAfterThrowing(String method,Exception e){ System.out.println(" 当前运算是 " + method + " , 抛的异常是: " + e); } }
计算器实现类:
public class Calculator implements Calculate { @Override public int add(int num1, int num2) { LogUtils.logBefore("加法" , num1 ,num2); int result = 0 ; try { result = num1 + num2; LogUtils.logAfterReturning("加法", result); } catch (Exception e) { LogUtils.logAfterThrowing("加法", e); throw new RuntimeException(e); } return result; } @Override public int add(int num1, int num2, int num3) { LogUtils.logBefore("加法" , num1 ,num2,num3); int result = 0 ; try { result = num1 + num2 + num3; LogUtils.logAfterReturning("加法", result); } catch (Exception e) { LogUtils.logAfterThrowing("加法", e); throw new RuntimeException(e); } return result; } @Override public int div(int num1, int num2) { LogUtils.logBefore("除法" , num1 ,num2); int result = 0 ; try { result = num1 / num2; LogUtils.logAfterReturning("除法", result); } catch (Exception e) { LogUtils.logAfterThrowing("除法", e); throw new RuntimeException(e); } return result; } }
计算器测试:
public class CalculateTest { @Test public void test1() { Calculate calculate = new Calculator(); /** * 需求 1: 要求在运算方法(add或div)计算之前记录一下当前方法的运算和运算数 <br/> * 需求 2: 要求修改日记的文本内容 <br/> * 需要 3: 要求在方法运算之后,记录一下当前方法和运算的结果 <br/> * 要求 4: 要求在方法运算过程中,记录下抛出的异常和方法名<br/> * 要求 5: 要求当前的项目中,有很多很多个这样的类(上千个).要求这些类的每个方法都做相同的处理 */ int add = calculate.add(100, 100); System.out.println(add); System.out.println("============================"); int div = calculate.div(100,0); System.out.println(div); } }
9.4、使用代理实现日记
9.4.1、使用jdk动态代理统一日记
package com.demo.proxy; import com.demo.pojo.Calculate; import com.demo.pojo.Calculator; import com.demo.util.LogUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxyFactory { /** * 创建jdk动态代理实现类 * @return */ public static Object createJdkProxyInstance(Object target){ /** * 需要使用JDK中的一个工具类,代理对象是对目标对象做了一个包装, * 代理的目的是为了给目标对象增加额外功能.这些额外功能操作就叫增强(方法增强)<br/> * Proxy是一个反射包下的工具类.它的作用就是创建jdk动态代理对象<br/> * newProxyInstance()方法就是用来创建代理对象实例的<br/> * 第一个参数是: 类加载器(一般放目标对象的类加载器) <br/> * 第二个参数是: 是目标对象实现了的所有接口(代理对象实例也会实现) <br/> * 第三个参数是: InvocationHandler接口实现类( 这个接口,就是用来给目标方法实现额外功能的接口 )<br/> **/ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { /** * invoke方法是代理对象调用方法时,就会执行的方法(不管代理对象调用什么方法,都会执行invoke()方法)<br/> * invoke()方法要负责对原来的方法进行增强(在完成原来方法的功能上,做额外操作 <br/> * @param proxy 是代理对象实例<br/> * @param method 调用方法的反射对象实例<br/> * @param args 调用方法时传递进来的参数<br/> * @return invoke方法的返回值就是代理调用的方法的返回值<br/> * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // @param method 调用方法的反射对象实例<br/> // System.out.println( method ); // @param args 调用方法时传递进来的参数<br/> // System.out.println(Arrays.asList(args)); // 需要1 : 执行目标对象方法 // 需要2 : 给目标方法做增强操作( 日记操作 ) LogUtils.logBefore(method.getName(), args);// 前置增强 (在目标方法前做的额外操作) Object result = null; try { /** * method.invoke()通过反射调用方法<br/> * 第一个参数是方法的实例对象 <br/> * 第二个参数是方法调用时的参数 <br/> * method.invoke() 返回值就是调用的方法的返回值<br/> */ result = method.invoke( target , args ); System.out.println( " method.invoke() 方法的返回值 ===>>> " + result ); LogUtils.logAfterReturning(method.getName(), result); } catch (Exception e) { LogUtils.logAfterThrowing(method.getName(), e);// 异常增强 throw new RuntimeException(e); } return result; } }); } public static void main(String[] args) { // 这是目标对象 Calculate target = new Calculator(); // 使用createJdkProxyInstance()创建jdk动态代理对象实例 Calculate jdkProxyInstance = (Calculate) createJdkProxyInstance(target); // 代理对象是接口的一个实现类 System.out.println( jdkProxyInstance instanceof Calculate );// true 是接口的一个实现类 System.out.println( jdkProxyInstance instanceof Calculator );// false 这是Calcuetor的子类 // 通过代理调用方法 // int result = jdkProxyInstance.div(100,10); // System.out.println( "结果是: " + result ); // 代理可以给很多很多这样的类都一次性实现添加日记的操作 // IShow showTarget = new ChengLong(); // IShow proxy = (IShow) createJdkProxyInstance(showTarget); // proxy.show(" 成龙 "); } }
优点:这种方式已经解决我们前面所有日记需要的问题。非常的灵活。而且可以方便的在后期进行维护和升级。
缺点:当然使用jdk动态代理,需要有接口。如果没有接口。就无法使用jdk动态代理。
Spring-03
9、AOP切面编程
9.1、什么是AOP
AOP是面向切面编程。全称:Aspect Oriented Programming
面向切面编程指的是:程序是运行期间,动态地将某段代码插入到原来方法代码的某些位置中。这就叫面向切面编程。
9.2、一个简单计算数功能加日记
计算器接口
public interface Calculate { public int add(int num1, int num2); public int add(int num1, int num2, int num3); public int div(int num1, int num2); }
日记工具类:
public class LogUtils { public static void logBefore(String method,Object ... args){ System.out.println(" 当前运算是 " + method + " , 参数是: " + Arrays.asList(args)); } public static void logAfterReturning(String method,Object result){ System.out.println(" 当前运算是 " + method + " , 结果是: " + result); } public static void logAfterThrowing(String method,Exception e){ System.out.println(" 当前运算是 " + method + " , 抛的异常是: " + e); } }
计算器实现类:
public class Calculator implements Calculate { @Override public int add(int num1, int num2) { LogUtils.logBefore("加法" , num1 ,num2); int result = 0 ; try { result = num1 + num2; LogUtils.logAfterReturning("加法", result); } catch (Exception e) { LogUtils.logAfterThrowing("加法", e); throw new RuntimeException(e); } return result; } @Override public int add(int num1, int num2, int num3) { LogUtils.logBefore("加法" , num1 ,num2,num3); int result = 0 ; try { result = num1 + num2 + num3; LogUtils.logAfterReturning("加法", result); } catch (Exception e) { LogUtils.logAfterThrowing("加法", e); throw new RuntimeException(e); } return result; } @Override public int div(int num1, int num2) { LogUtils.logBefore("除法" , num1 ,num2); int result = 0 ; try { result = num1 / num2; LogUtils.logAfterReturning("除法", result); } catch (Exception e) { LogUtils.logAfterThrowing("除法", e); throw new RuntimeException(e); } return result; } }
计算器测试:
public class CalculateTest { @Test public void test1() { Calculate calculate = new Calculator(); /** * 需求 1: 要求在运算方法(add或div)计算之前记录一下当前方法的运算和运算数 <br/> * 需求 2: 要求修改日记的文本内容 <br/> * 需要 3: 要求在方法运算之后,记录一下当前方法和运算的结果 <br/> * 要求 4: 要求在方法运算过程中,记录下抛出的异常和方法名<br/> * 要求 5: 要求当前的项目中,有很多很多个这样的类(上千个).要求这些类的每个方法都做相同的处理 */ int add = calculate.add(100, 100); System.out.println(add); System.out.println("============================"); int div = calculate.div(100,0); System.out.println(div); } }
9.4、使用代理实现日记
9.4.1、使用jdk动态代理统一日记
package com.demo.proxy; import com.demo.pojo.Calculate; import com.demo.pojo.Calculator; import com.demo.util.LogUtils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class JdkProxyFactory { /** * 创建jdk动态代理实现类 * @return */ public static Object createJdkProxyInstance(Object target){ /** * 需要使用JDK中的一个工具类,代理对象是对目标对象做了一个包装, * 代理的目的是为了给目标对象增加额外功能.这些额外功能操作就叫增强(方法增强)<br/> * Proxy是一个反射包下的工具类.它的作用就是创建jdk动态代理对象<br/> * newProxyInstance()方法就是用来创建代理对象实例的<br/> * 第一个参数是: 类加载器(一般放目标对象的类加载器) <br/> * 第二个参数是: 是目标对象实现了的所有接口(代理对象实例也会实现) <br/> * 第三个参数是: InvocationHandler接口实现类( 这个接口,就是用来给目标方法实现额外功能的接口 )<br/> **/ return Proxy.newProxyInstance( target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() { /** * invoke方法是代理对象调用方法时,就会执行的方法(不管代理对象调用什么方法,都会执行invoke()方法)<br/> * invoke()方法要负责对原来的方法进行增强(在完成原来方法的功能上,做额外操作 <br/> * @param proxy 是代理对象实例<br/> * @param method 调用方法的反射对象实例<br/> * @param args 调用方法时传递进来的参数<br/> * @return invoke方法的返回值就是代理调用的方法的返回值<br/> * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // @param method 调用方法的反射对象实例<br/> // System.out.println( method ); // @param args 调用方法时传递进来的参数<br/> // System.out.println(Arrays.asList(args)); // 需要1 : 执行目标对象方法 // 需要2 : 给目标方法做增强操作( 日记操作 ) LogUtils.logBefore(method.getName(), args);// 前置增强 (在目标方法前做的额外操作) Object result = null; try { /** * method.invoke()通过反射调用方法<br/> * 第一个参数是方法的实例对象 <br/> * 第二个参数是方法调用时的参数 <br/> * method.invoke() 返回值就是调用的方法的返回值<br/> */ result = method.invoke( target , args ); System.out.println( " method.invoke() 方法的返回值 ===>>> " + result ); LogUtils.logAfterReturning(method.getName(), result); } catch (Exception e) { LogUtils.logAfterThrowing(method.getName(), e);// 异常增强 throw new RuntimeException(e); } return result; } }); } public static void main(String[] args) { // 这是目标对象 Calculate target = new Calculator(); // 使用createJdkProxyInstance()创建jdk动态代理对象实例 Calculate jdkProxyInstance = (Calculate) createJdkProxyInstance(target); // 代理对象是接口的一个实现类 System.out.println( jdkProxyInstance instanceof Calculate );// true 是接口的一个实现类 System.out.println( jdkProxyInstance instanceof Calculator );// false 这是Calcuetor的子类 // 通过代理调用方法 // int result = jdkProxyInstance.div(100,10); // System.out.println( "结果是: " + result ); // 代理可以给很多很多这样的类都一次性实现添加日记的操作 // IShow showTarget = new ChengLong(); // IShow proxy = (IShow) createJdkProxyInstance(showTarget); // proxy.show(" 成龙 "); } }
jdk动态代理技术它的优点是,已经可以很好的解决一次性给很多类的所有方法都添加上日记的需求.
缺点是: 如果目标对象没有实现接口.jdk动态代理技术就无法使用.
9.4.2、使用Cglib代理 ( 了解内容 )
Cglib动态代理它不管目标对象有没有实现接口.它都可以实现代理技术.
Cglib动态代理是通过修改目标对象的字节码程序产生一个子类.生成一个代理对象实例.
Cglib产生的代理对象是目标对象的子类
package com.demo.proxy; import com.demo.pojo.Calculate; import com.demo.pojo.Calculator; import com.demo.util.LogUtils; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; public class CglibProxyFactory { public static Object createCglibProxy(Object target){ // 增强器 它负责产生一个Cglib代理对象实例 Enhancer enhancer = new Enhancer(); // 指定要修改哪个目标对象的字节码程序 enhancer.setSuperclass(target.getClass()); // 设置方法拦截器==跟InvocationHandler接口功能一样,是代理对象调用方法时就会执行的接口(专门对目标方法进行增强) enhancer.setCallback(new MethodInterceptor() { /** * 只要代理对象方法调用,就会执行intercept()方法 * @param proxy 代理对象实例 <br/> * @param method 调用的方法的反射对象 <br/> * @param args 调用方法时传递的参数 <br/> * @param methodProxy 方法反射对象的代理对象<br/> * @return 返回值是代理对象调用方法的返回值 * @throws Throwable */ @Override public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { LogUtils.logBefore(method.getName(), args);// 前置增强 (在目标方法前做的额外操作) Object result = null; try { /** * method.invoke()通过反射调用方法<br/> * 第一个参数是方法的实例对象 <br/> * 第二个参数是方法调用时的参数 <br/> * method.invoke() 返回值就是调用的方法的返回值<br/> */ result = method.invoke( target , args ); System.out.println( " method.invoke() 方法的返回值 ===>>> " + result ); LogUtils.logAfterReturning(method.getName(), result); // 返回增强 } catch (Exception e) { LogUtils.logAfterThrowing(method.getName(), e);// 异常增强 throw new RuntimeException(e); } return result; } }); // 创建Cglib代理对象实例 return enhancer.create(); } public static void main(String[] args) { Calculator calculator = new Calculator(); Calculator proxy = (Calculator) createCglibProxy(calculator); int add = proxy.add(100, 100); System.out.println(add); System.out.println( proxy instanceof Calculate); System.out.println( proxy instanceof Calculator); } }
优点:在没有接口的情况下,同样可以实现代理的效果。
缺点:同样需要自己编码实现代理全部过程。
但是为了更好的整合Spring框架使用。所以我们需要学习一下Spring 的AOP 功能。也就是学习Spring提供的AOP功能。
9.5、AOP编程的专业术语
通知(Advice)
通知就是增强的代码。比如前置增强的代码。后置增强的代码。异常增强代码。这些就叫通知
切面(Aspect)
切面就是包含有通知代码的类叫切面。
横切关注点
横切关注点,就是我们可以添加增强代码的位置。比如前置位置,后置位置,异常位置。和返回值位置。这些都叫横切关注点。
目标(Target)
目标对象就是被关注的对象。或者被代理的对象。
代理(Proxy)
为了拦截目标对象方法,而被创建出来的那个对象,就叫做代理对象。
连接点(Joinpoint)
连接点指的是横切关注点和程序代码的连接,叫连接点。
切入点(pointcut)
切入点指的是用户真正处理的连接点,叫切入点。
在Spring中切入点通过org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件。
9.6、使用Spring实现AOP简单切面编程
需要导哪些包:
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
junit_4.12.jar
org.hamcrest.core_1.3.0.jar
spring-aop-5.2.5.RELEASE.jar
spring-aspects-5.2.5.RELEASE.jar
spring-beans-5.2.5.RELEASE.jar
spring-context-5.2.5.RELEASE.jar
spring-core-5.2.5.RELEASE.jar
spring-expression-5.2.5.RELEASE.jar
spring-jcl-5.2.5.RELEASE.jar
Spring配置文件内容:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://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.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 包扫描==因为使用了注解 --> <context:component-scan base-package="com"></context:component-scan> <!-- aop:aspectj-autoproxy是跟注解@Aspect一起组合使用 --> <aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>
测试代码:
public class SpringTest { @Test public void test() { // 创建Spring容器对象 ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); Calculate calculate = (Calculate) applicationContext.getBean("calculator"); calculate.add(100,100); System.out.println("==========================="); calculate.add(100,100,100); System.out.println("============================"); calculate.div(100,1); } }
9.7、Spring的切入点表达式
@PointCut切入点表达式语法格式是: execution(访问权限 返回值类型 方法全限定名(参数类型列表))
execution(public int com.demo.pojo.Calculator.add(int,int))
public 访问权限
int 返回值类型
com.demo.pojo 包名
Calculator 类名
add 方法名
(int,int) 参数类型列表
限定符:
*表示任意的意思:
- 匹配某全类名下,任意或多个方法。
execution(public int com.demo.pojo.Calculator.*(int,int))
以上的星表示方法名任意
- 在Spring中只有public权限能拦截到,访问权限可以省略(访问权限不能写*)。
execution( int com.demo.pojo.Calculator.add(int,int))
以上切入点表达式相当于:
execution( public int com.demo.pojo.Calculator.add(int,int))
- 匹配任意类型的返回值,可以使用 * 表示
execution(public * com.demo.pojo.Calculator.add(int,int))
以上的星表示返回值类型任意.
- 匹配任意一层子包。
execution(public int com.demo.*.Calculator.add(int,int))
以上的星表示
包必须是com.demo.子包
而且只能是一层的子包
- 任意类型参数
execution(public int com.demo.pojo.Calculator.add(int,*))
以上的星表示第二个参数类型任意.
..:可以匹配多层路径,或任意多个任意类型参数
- 任意层级的包
execution(public int com.demo..Calculator.add(int,int))
以上的..表示 包名必须是 com.demo.所有子包都匹配
- 任意个任意类型的参数
execution(public int com.demo.pojo.Calculator.add(..))
以上的..表示参数是任意个数,参数是任意的参数
模糊匹配:
// 表示任意返回值,任意方法全限定符,任意参数
execution(* *(..))
// 表示任意返回值,任意包名+任意方法名,任意参数
execution(* *.*(..))
精确匹配:
execution(public int com.demo.pojo.Calculator.add(int,int))
访问权限是public类型
返回值必须是int类型
包名必须是com.demo.pojo
类名必须是Calculator
方法名必须是add
参数必须是两个int类型
切入点表达式连接:&& 、||
// 表示需要同时满足两个表达式
@Before("execution(public int com.demo.aop.Calculator.add(int, int))"
+ " && "
+ "execution(public * com.demo.aop.Calculator.add(..))")
// 表示两个条件只需要满足一个,就会被匹配到
@Before("execution(public int com.demo.aop.Calculator.add(int, int))"
+ " || "
+ "execution(public * com.demo.aop.Calculator.a*(int))")
9.8、Spring切面中的代理对象
在Spring中,可以对有接口的对象和无接口的对象分别进行代理。在使用上有些细微的差别。
1) 如果被代理的对象实现了接口。在获取对象的时候,必须要以接口来接收返回的对象。
2) 如果被代理对象,如果没有实现接口。获取对象的时候使用对象类型本身
9.9、Spring通知的执行顺序
Spring AOP编程中提供的常用通知有四种 , 分别是 : 前置通知 , 后置通知 , 返回通知 , 异常通知
Spring通知的执行顺序是:
正常情况:
前置通知====>>>>目标方法====>>>>后置通知=====>>>>返回值之后
异常情况:
前置通知====>>>>目标方法====>>>>后置通知=====>>>>抛异常通知
@Component @Aspect//表示LogUtils是一个切面类 public class LogUtils { /** * @Before 表示前置通知 <br/> * execution(public int com.demo.pojo.Calculator.add(int,int))是切入点表达式,表示通知给哪些方法使用 <br/> * 切入点表达式的语法结构是: execution( 方法访问权限 方法返回值 包名+类名+方法名(参数类型列表) ) <br/> */ @Before(value = "execution(public int com.demo.pojo.Calculator.*(int,int))") public static void logBefore(){ System.out.println(" 前置通知 " + " , 参数是: " ); } /** * @After是后置通知 <br/> */ @After(value = "execution(public int com.demo.pojo.Calculator.*(int,int))") public static void logAfter(){ System.out.println(" 后置通知 " + " , 参数是: " ); } /** * @AfterReturning 返回通知 <br/> */ @AfterReturning(value = "execution(public int com.demo.pojo.Calculator.*(int,int))") public static void logAfterReturning(){ System.out.println(" 返回通知 " + " , 结果是: " ); } /** *@AfterThrowing 是异常通知 <br> */ @AfterThrowing(value = "execution(public int com.demo.pojo.Calculator.*(int,int))") public static void logAfterThrowing(){ System.out.println(" 异常通知 " + " , 抛的异常是: "); } }
9.10、获取连接点信息
JoinPoint 是连接点的信息。
只需要在通知方法的参数中,加入一个JoinPoint参数。就可以获取到拦截方法的信息。
注意:是org.aspectj.lang.JoinPoint这个类。
/** * @Before 表示前置通知 <br/> * execution(public int com.demo.pojo.Calculator.add(int,int))是切入点表达式,表示通知给哪些方法使用 <br/> * 切入点表达式的语法结构是: execution( 方法访问权限 方法返回值 包名+类名+方法名(参数类型列表) ) <br/> */ @Before(value = "execution(public int com.demo.pojo.Calculator.*(int,int))") public static void logBefore(JoinPoint joinPoint){ /** * joinPoint.getSignature().getName() 获取方法名 <br/> * joinPoint.getArgs() 获取方法的参数 <br/> */ System.out.println(" 前置通知 方法名是:" + joinPoint.getSignature().getName() + " , 参数是: " + Arrays.asList(joinPoint.getArgs())); }
9.11、获取拦截方法的返回值和抛的异常信息
获取方法返回的值分为两个步骤:
1、在返回值通知的方法中,追加一个参数 Object result
2、然后在@AfterReturning注解中添加参数returning="参数名"
/** * @AfterReturning 返回通知 <br/> * 1 在返回通知上追加一个参数Object result <br/> * 2 在返回通知注解@AfterReturning中使用属性returning = "result"告诉Spring框架,返回值交给哪个参数去接收 <br/> */ @AfterReturning(value = "execution(public int com.demo.pojo.Calculator.*(int,int))",returning = "result") public static void logAfterReturning(JoinPoint joinPoint , Object result){ System.out.println(" 返回通知 方法名是:" + joinPoint.getSignature().getName() + " , 结果是: " + result); }
获取方法抛出的异常分为两个步骤:
- 在异常通知的方法中,追加一个参数Exception exception
- 然后在@AfterThrowing 注解中添加参数 throwing="参数名"
/** *@AfterThrowing 是异常通知 <br> * 1 在异常通知方法上追加一个参数Exception e 用来接收抛出的异常<br/> * 2 在异常通知注解@AfterThrowing上使用属性throwing = "e",告诉Spring抛出的异常用哪个参数来接收<br/> */ @AfterThrowing(value = "execution(public int com.demo.pojo.Calculator.*(int,int))",throwing = "e") public static void logAfterThrowing(JoinPoint joinPoint , Exception e){ System.out.println(" 异常通知 方法名是: " + joinPoint.getSignature().getName() + " , 抛的异常是: " + e); }
9.12、Spring的环绕通知
- 环绕通知使用@Around注解。
- 环绕通知如果和其他通知同时执行。环绕通知会优先于其他通知之前执行。
- 环绕通知一定要有返回值(环绕如果没有返回值。后面的其他通知就无法接收到目标方法执行的结果)。
- 在环绕通知中。如果拦截异常。一定要往外抛。否则其他的异常通知是无法捕获到异常的。
/** * 环绕通知 <br/> * 1 使用注解@Around表示环绕通知<br/> * 2 环绕通知需要添加一个ProceedingJoinPoint proceedingJoinPoint参数.它可以用来执行目标方法<br/> * 3 环绕通知一定要把目标方法的返回值返回.否则普通的返回通知收不到结果<br/> * 4 环绕通知捕获到异常,一定要往外抛.否则普通的异常通知不执行<br/> * 5 环绕通知会比普通通知优先执行<br/> */ @Around(value = "execution(public int com.demo.pojo.Calculator.*(int,int))") public static Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable { Object result = null; try { try { System.out.println(" 环绕前置 " ); // 执行了目标方法 result = proceedingJoinPoint.proceed(); } finally { System.out.println(" 环绕后置 "); } System.out.println(" 环绕返回 "); } catch (Throwable throwable) { System.out.println(" 环绕异常 "); throwable.printStackTrace(); throw throwable; } return result; }
9.13、切入点表达式的复用
9.14、多个切面的执行顺序
当我们有多个切面,多个通知的时候:
- 通知的执行顺序默认是由切面类的字母先后顺序决定。
- 在切面类上使用@Order注解决定通知执行的顺序(值越小,越先执行)
9.15、如何基于xml配置aop程序
注意:
1 一定要先把原来注解版本中所有注解都去掉
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 配置目标对象实例 --> <bean class="com.demo.pojo.Calculator" id="calculator" /> <!-- 配置切面类的实例 --> <bean class="com.demo.util.LogUtils" id="logUtils" /> <!-- AOP配置 ==> 就是代理 --> <aop:config> <!-- 配置切面 --> <aop:aspect ref="logUtils" > <aop:pointcut id="pointcut1" expression="execution(public int com.demo.pojo.Calculator.*(..))" /> <!-- aop:before标签跟注解@Before一样,都是前置通知<br/> method 是前置通知方法名 pointcut 是切入点表达式 --> <aop:before method="logBefore" pointcut-ref="pointcut1" /> <!-- aop:after 标签跟注解 @After 一样,都是后置通知<br/> method 是通知方法名 pointcut 是切入点表达式 --> <aop:after method="logAfter" pointcut-ref="pointcut1" /> <!-- 返回通知 --> <aop:after-returning method="logAfterReturning" returning="result" pointcut-ref="pointcut1" /> <!-- 异常通知 --> <aop:after-throwing method="logAfterThrowing" throwing="e" pointcut-ref="pointcut1" /> </aop:aspect> </aop:config> </beans>
- Spring之数据访问
10.1、Spring数据访问工程环境搭建
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
druid-1.1.9.jar
junit_4.12.jar
mysql-connector-java-5.1.37-bin.jar
org.hamcrest.core_1.3.0.jar
spring-aop-5.2.5.RELEASE.jar
spring-aspects-5.2.5.RELEASE.jar
spring-beans-5.2.5.RELEASE.jar
spring-context-5.2.5.RELEASE.jar
spring-core-5.2.5.RELEASE.jar
spring-expression-5.2.5.RELEASE.jar
spring-jcl-5.2.5.RELEASE.jar
spring-jdbc-5.2.5.RELEASE.jar
spring-orm-5.2.5.RELEASE.jar
spring-test-5.2.5.RELEASE.jar
spring-tx-5.2.5.RELEASE.jar
10.2、Spring之JdbcTemplate使用
在Spring中提供了对jdbc的封装类叫JdbcTemplate。它可以很方便的帮我们执行sql语句,操作数据库。
先准备单表的数据库数据
drop database if exists jdbctemplate;
create database jdbctemplate;
use jdbctemplate;
CREATE TABLE `employee` (
`id` int(11) primary key AUTO_INCREMENT,
`name` varchar(100) DEFAULT NULL,
`salary` decimal(11,2) DEFAULT NULL
);
insert into `employee`(`id`,`name`,`salary`)
values (1,'李三',5000.23),(2,'李四',4234.77),(3,'王五',9034.51),
(4,'赵六',8054.33),(5,'孔七',6039.11),(6,'曹八',7714.11);
select * from employee;
JdbcTemplate的使用需要在applicationContext.xml中进行配置
<!-- jdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
实验2:将id=5的记录的salary字段更新为1300.00
@Test public void test2() { String sql = "update employee set salary = ? where id = ?"; /** * update方法执行 insert, update,delete语句<br/> * 第一个参数是sql语句 <br/> * 第二个参数是占位符?的值 <b/> */ int count = jdbcTemplate.update(sql,new BigDecimal(1300),5 ); System.out.println( count ); }
实验3:批量插入
@Test public void test3() { String sql = "insert into employee(`name`,`salary`) values(?,?)";// ==>> 一个sql语句的,它的参数是一维数组 /** * batchUpdate批量的执行insert,update,delete语句 <br/> * 第一个参数是 sql 语句 <br/> * 第二个参数是 所有sql语句的占位符的值<br/> */ List<Object[]> batchArgs = new ArrayList<>();//有几个一维数组,就插入几条记录 batchArgs.add(new Object[]{"111111",new BigDecimal(10000)}); batchArgs.add(new Object[]{"222222",new BigDecimal(20000)}); batchArgs.add(new Object[]{"333333",new BigDecimal(30000)}); batchArgs.add(new Object[]{"444444",new BigDecimal(40000)}); int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs); }
实验4:查询id=5的数据库记录,封装为一个Java对象返回
public class Employee { private Integer id; private String name; private BigDecimal salary;
@Test public void test4() { String sql = "select `id`,`name`,`salary` from employee where id = ?"; /** * 查询使用queryForObject()方法查询返回一条记录 <br/> * 第一个参数是sql语句<br/> * 第二个参数是 RowMapper接口,负责把查询结果集中每一行记录转换为JavaBean对象<br/> * 第三个参数是占位符的值<br/> */ /** * BeanPropertyRowMapper它负责将结果集中的列和属性做对应操作(将一行记录转换为JavaBean) */ Employee employee = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Employee>(Employee.class), 5); System.out.println( employee ); }
实验5:查询salary>4000的数据库记录,封装为List集合返回
@Test public void test5() { String sql = "select `id`,`name`,`salary` from employee where salary > ?"; List<Employee> employeeList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Employee>(Employee.class), new BigDecimal(4000)); for (Employee employee : employeeList) { System.out.println(employee); } }
JdbcTemplate小结 :
/** * JdbcTemplate和之前的DbUtils QueryRunner工具类非常像 <br/> * QueryRunner.update() 执行insert,delete,update的sql语句<br/> * query() 执行select查询 <br/> * QueryRunner 执行查询一条记录还是多条记录.由ResultSetHandler实现类决定<br/> * BeanHandler查一条记录><br/> * BeanListHandler查询多条记录<br/> * JdbcTemplate.update() 执行insert,delete,update的sql语句<br/> * JdbcTemplate.queryForObject() 查询返回一行数据的方法<br/> * JdbcTemplate.query() 查询多行数据<br/> * JdbcTemplate中是使用BeanPropertyRowMapper将每笔记录转换为JavaBean<br/> */
实验6:查询最大salary
@Test public void test6() { String sql = "select max(salary) from employee"; /** * 第一个参数是sql语句,第二个参数是返回的数据类型<br/> */ BigDecimal max = jdbcTemplate.queryForObject(sql, BigDecimal.class); System.out.println(max); }
实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值
配置内容
<!-- 配置可执行具名参数sql的工具类 --> <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <constructor-arg index="0" ref="dataSource" /> </bean>
测试代码:
@Autowired NamedParameterJdbcTemplate namedParameterJdbcTemplate; // 实验7:使用带有具名参数的SQL语句插入一条员工记录,并以Map形式传入参数值 @Test public void test7() { /** * 具名参数sql中的占位符格式如下 ==>> :参数名 */ String sql = "insert into employee(`name`,`salary`) values( :name , :salary )";// 具名参数的SQL Map<String,Object> paramMap = new HashMap<>();// map的key要和参数名相同 paramMap.put("name","具名参数"); paramMap.put("salary",new BigDecimal("9.9")); int update = namedParameterJdbcTemplate.update(sql, paramMap); }
实验8:创建Dao,自动装配JdbcTemplate对象
@Repository public class EmployeeDao { @Autowired JdbcTemplate jdbcTemplate; public Employee selectEmployeeById(Integer id){ String sql = "select `id`,`name`,`salary` from employee where id = ?"; return jdbcTemplate.queryForObject(sql,new BeanPropertyRowMapper<Employee>(Employee.class),id); } }
测试代码:
@Autowired EmployeeDao employeeDao; // 实验9:创建Dao,自动装配JdbcTemplate对象 @Test public void test9() { System.out.println(employeeDao.selectEmployeeById(1)); }
实验9:通过继承JdbcDaoSupport创建JdbcTemplate的Dao
@Repository public class EmployeeDao extends JdbcDaoSupport { @Autowired public void abc(DataSource dataSource){ setDataSource(dataSource); } public Employee selectEmployeeById(Integer id){ String sql = "select `id`,`name`,`salary` from employee where id = ?"; return getJdbcTemplate() .queryForObject(sql, new BeanPropertyRowMapper<Employee>(Employee.class),id); } }
Spring-04
- 声明式事务
事务分为声明式和编程式两种:
声明式事务:声明式事务是指通过注解的形式或xml配置的形式对事务的各种特性进行控制和管理。
编码式(编程式)事务:指的是通过编码的方式实现事务的声明。
11.1、编码方式实现事务:
Spring实现编程式事务,依赖于2大类,分别是上篇文章提到的PlatformTransactionManager,与模版类TransactionTemplate(推荐使用)。下面分别详细介绍Spring是如何通过该类实现事务管理。
1)PlatformTransactionManager在springboot中的使用
2)PlatformTransactionManager
Spring在事务管理时,对事务的处理做了极致的抽象,即PlatformTransactionManager。对事务的操作,简单地来说,只有三步操作:获取事务,提交事务,回滚事务。
public interfacePlatformTransactionManager{ // 获取事务 TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException; // 提交事务 voidcommit(TransactionStatus status)throws TransactionException; // 回滚事务 voidrollback(TransactionStatus status)throws TransactionException; }
当然Spring不会仅仅只提供一个接口,同时会有一个抽象模版类,实现了事务管理的具体骨架。AbstractPlatformTransactionManager类可以说是Spring事务管理的控制台,决定事务如何创建,提交和回滚。
在Spring事务管理(二)-TransactionProxyFactoryBean原理中,分析TransactionInterceptor增强时,在invoke方法中最重要的三个操作:
创建事务 createTransactionIfNecessary
异常后事务处理 completeTransactionAfterThrowing
方法执行成功后事务提交 commitTransactionAfterReturning
在具体操作中,最后都是通过事务管理器PlatformTransactionManager的接口实现来执行的,其实也就是上面列出的三个接口方法。我们分别介绍这三个方法的实现,并以DataSourceTransactionManager为实现类观察JDBC方式事务的具体实现。
1. 获取事务
getTransaction方法根据事务定义来获取事务状态,事务状态中记录了事务定义,事务对象及事务相关的资源信息。对于事务的获取,除了调用事务管理器的实现来获取事务对象本身外,另外的很重要的一点是处理了事务的传播方式。
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)throws TransactionException { // 1.获取事务对象 Object transaction = doGetTransaction(); // Cache debug flag to avoid repeated checks. boolean debugEnabled = logger.isDebugEnabled(); if (definition == null) { // Use defaults if no transaction definition given. definition = new DefaultTransactionDefinition(); } // 2.如果已存在事务,根据不同的事务传播方式处理获取事务 if (isExistingTransaction(transaction)) { // Existing transaction found -> check propagation behavior to find out how to behave. return handleExistingTransaction(definition, transaction, debugEnabled); } // Check definition settings for new transaction. if (definition.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) { throw new InvalidTimeoutException("Invalid transaction timeout", definition.getTimeout()); } // 3. 如果当前没有事务,不同的事务传播方式不同处理方式 // 3.1 事务传播方式为mandatory(强制必须有事务),则抛出异常 // No existing transaction found -> check propagation behavior to find out how to proceed. if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) { throw new IllegalTransactionStateException( "No existing transaction found for transaction marked with propagation 'mandatory'"); } // 3.2 事务传播方式为required或required_new或nested(嵌套),创建一个新的事务状态 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { SuspendedResourcesHolder suspendedResources = suspend(null); if (debugEnabled) { logger.debug("Creating new transaction with name [" + definition.getName() + "]: " + definition); } try { boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); // 创建新的事务状态对象 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); // 事务初始化 doBegin(transaction, definition); // 准备其他同步操作 prepareSynchronization(status, definition); return status; } catch (RuntimeException | Error ex) { resume(null, suspendedResources); throw ex; } } // 3.3 其他事务传播方式,返回一个事务对象为null的事务状态对象 else { // Create "empty" transaction: no actual transaction, but potentially synchronization. if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT && logger.isWarnEnabled()) { logger.warn("Custom isolation level specified but no actual transaction initiated; " + "isolation level will effectively be ignored: " + definition); } boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS); return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null); } }
获取事务的方法主要做两件事情:
- 获取事务对象
- 根据事务传播方式返回事务状态对象
获取事务对象,在DataSourceTransactionManager的实现中,返回一个DataSourceTransactionObject对象
protected Object doGetTransaction(){ DataSourceTransactionObject txObject = new DataSourceTransactionObject(); txObject.setSavepointAllowed(isNestedTransactionAllowed()); ConnectionHolder conHolder = (ConnectionHolder) // 从事务同步管理器中根据DataSource获取数据库连接资源 TransactionSynchronizationManager.getResource(obtainDataSource()); txObject.setConnectionHolder(conHolder, false); return txObject; }
每次执行doGetTransaction方法,即会创建一个DataSourceTransactionObject对象txObject,并从事务同步管理器中根据DataSource获取数据库连接持有对象ConnectionHolder,然后存入txObject中。**事务同步管理类持有一个ThreadLocal级别的resources对象,存储DataSource和ConnectionHolder的映射关系。**因此返回的txObject中持有的ConnectionHolder可能有值,也可能为空。而不同的事务传播方式下,事务管理的处理根据txObejct中是否存在事务有不同的处理方式。
关于关注事务传播方式的实现,很多人对事务传播方式都是一知半解,只是因为没有了解源码的实现。现在就来看看具体的实现。事务传播方式的实现分为两种情况,事务不存在和事务已经存在。isExistingTransaction方法判断事务是否存在,默认在AbstractPlatformTransactionManager抽象类中返回false,而在DataSourceTransactionManager实现中,则根据是否有数据库连接来决定。
protectedbooleanisExistingTransaction(Object transaction){ DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; return (txObject.hasConnectionHolder() && txObject.getConnectionHolder().isTransactionActive()); }
事务管理器配置 <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="jdbcUrl" value="${db.jdbcUrl}" /> <property name="user" value="${user}" /> <property name="password" value="${password}" /> <property name="driverClass" value="${db.driverClass}" /> <!--连接池中保留的最小连接数。 --> <property name="minPoolSize"> <value>5</value> </property> <!--连接池中保留的最大连接数。Default: 15 --> <property name="maxPoolSize"> <value>30</value> </property> <!--初始化时获取的连接数,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> <property name="initialPoolSize"> <value>10</value> </property> <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> <property name="maxIdleTime"> <value>60</value> </property> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> <property name="acquireIncrement"> <value>5</value> </property> <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0 --> <property name="maxStatements"> <value>0</value> </property> <!--每60秒检查所有连接池中的空闲连接。Default: 0 --> <property name="idleConnectionTestPeriod"> <value>60</value> </property> <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> <property name="acquireRetryAttempts"> <value>30</value> </property> <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试获取连接失败后该数据源将申明已断开并永久关闭。Default: false --> <property name="breakAfterAcquireFailure"> <value>true</value> </property> <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable等方法来提升连接测试的性能。Default: false --> <property name="testConnectionOnCheckout"> <value>false</value> </property> </bean> <!--DataSourceTransactionManager位于org.springframework.jdbc.datasource包下,数据源事务管理类,提供对单个javax.sql.DataSource数据源的事务管理,主要用于JDBC,Mybatis框架事务管理。 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource" /> </bean>
业务中使用代码(以测试类展示) import java.util.Map; import javax.annotation.Resource; import javax.sql.DataSource; import org.apache.log4j.Logger; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:spring-public.xml" }) public class test { @Resource private PlatformTransactionManager txManager; @Resource private DataSource dataSource; private static JdbcTemplate jdbcTemplate; Logger logger=Logger.getLogger(test.class); private static final String INSERT_SQL = "insert into testtranstation(sd) values(?)"; private static final String COUNT_SQL = "select count(*) from testtranstation"; @Test public void testdelivery(){ //定义事务隔离级别,传播行为, DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); //事务状态类,通过PlatformTransactionManager的getTransaction方法根据事务定义获取;获取事务状态后,Spring根据传播行为来决定如何开启事务 TransactionStatus status = txManager.getTransaction(def); jdbcTemplate = new JdbcTemplate(dataSource); int i = jdbcTemplate.queryForInt(COUNT_SQL); System.out.println("表中记录总数:"+i); try { jdbcTemplate.update(INSERT_SQL, "1"); txManager.commit(status); //提交status中绑定的事务 } catch (RuntimeException e) { txManager.rollback(status); //回滚 } i = jdbcTemplate.queryForInt(COUNT_SQL); System.out.println("表中记录总数:"+i); } }
2).TransactionTemplate(推荐使用)
TransactionTemplate模板类使用的回调接口:
- TransactionCallback:通过实现该接口的“T doInTransaction(TransactionStatus status) ”方法来定义需要事务管理的操作代码;
- TransactionCallbackWithoutResult:继承TransactionCallback接口,提供“void doInTransactionWithoutResult(TransactionStatus status)”便利接口用于方便那些不需要返回值的事务操作代码。
还是以测试类方式展示如何实现
@Test public void testTransactionTemplate(){ jdbcTemplate = new JdbcTemplate(dataSource); int i = jdbcTemplate.queryForInt(COUNT_SQL); System.out.println("表中记录总数:"+i); //构造函数初始化TransactionTemplate TransactionTemplate template = new TransactionTemplate(txManager); template.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); //重写execute方法实现事务管理 template.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { jdbcTemplate.update(INSERT_SQL, "饿死"); //字段sd为int型,所以插入肯定失败报异常,自动回滚,代表TransactionTemplate自动管理事务 }} ); i = jdbcTemplate.queryForInt(COUNT_SQL); System.out.println("表中记录总数:"+i); }
11.2、声明式事务环境搭建
11.2.1、准备测试数据库
##创建tx数据库
drop database if exists `tx`;
CREATE database `tx`;
##切换tx数据库
USE `tx`;
##创建用户表
CREATE TABLE `user` (
`id` int primary key auto_increment,
`username` varchar(50) NOT NULL,
`money` int(11) DEFAULT NULL
);
##插入数据
insert into `user`(`username`,`money`) values ('张三',1000),('李四',1000);
##创建图书表
create table `book`(
`id` int primary key auto_increment,
`name` varchar(500) not null,
`stock` int
);
##插入数据
insert into book(`name`,`stock`) values('java编程思想',100),('C++编程思想',100);
##查看数据
select * from book;
select * from user;
11.2.2、创建一个Java工程,导入Jar包
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
druid-1.1.9.jar
junit_4.12.jar
mysql-connector-java-5.1.37-bin.jar
org.hamcrest.core_1.3.0.jar
spring-aop-5.2.5.RELEASE.jar
spring-aspects-5.2.5.RELEASE.jar
spring-beans-5.2.5.RELEASE.jar
spring-context-5.2.5.RELEASE.jar
spring-core-5.2.5.RELEASE.jar
spring-expression-5.2.5.RELEASE.jar
spring-jcl-5.2.5.RELEASE.jar
spring-jdbc-5.2.5.RELEASE.jar
spring-orm-5.2.5.RELEASE.jar
spring-test-5.2.5.RELEASE.jar
spring-tx-5.2.5.RELEASE.jar
jdbc.properties属性配置文件:
db.user=root db.password=root db.url=jdbc:mysql://localhost:3306/tx db.driverClassName=com.mysql.jdbc.Driver db.initialSize=5 db.maxActive=10
applicationContext.xml配置文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <!-- 加载jdbc.properties属性配置文件 --> <context:property-placeholder location="classpath:jdbc.properties" /> <!-- 配置包扫描 --> <context:component-scan base-package="com.demo" /> <!-- 数据库连接池 --> <bean class="com.alibaba.druid.pool.DruidDataSource" id="dataSource"> <property name="username" value="${db.user}"/> <property name="password" value="${db.password}"/> <property name="url" value="${db.url}"/> <property name="driverClassName" value="${db.driverClassName}"/> <property name="initialSize" value="${db.initialSize}"/> <property name="maxActive" value="${db.maxActive}"/> </bean> <!-- 配置sql执行的工具类 --> <bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> </beans>
Dao的代码:
@Repository public class BookDao { @Autowired JdbcTemplate jdbcTemplate; public void updateBook(){ jdbcTemplate.update("update book set `name` = '图书表被修改了'"); } }
@Repository//把当前类配置到Spring容器中 public class UserDao { @Autowired//从Spring容器中查找JdbcTemplate实例,并赋值属性 JdbcTemplate jdbcTemplate; public void updateUser(){ jdbcTemplate.update("update user set `username` = '用户表被修改了'" ); } }
Service的代码:
@Service//把当前类配置到Spring容器中 public class TransactionService { @Autowired UserDao userDao; @Autowired BookDao bookDao; public void multiUpdate(){ userDao.updateUser(); bookDao.updateBook(); } }
默认的情况下dao中一个方法执行完就生效.
Service方法不带有事务
11.3、测试Service的默认事务
实验1:测试service服务层的默认事务
@Service//把当前类配置到Spring容器中 public class TransactionService { @Autowired UserDao userDao; @Autowired BookDao bookDao; public void multiUpdate(){ userDao.updateUser(); int i = 12 / 0; bookDao.updateBook(); } }
异常的演示
以上操作user表成功
Spring事务引入的分析------PlatformTransactionManager类简单介绍
PlatformTransactionManager是一个接口.
这个接口是Spring中提供的专门用来处理事务的接口.
public interface PlatformTransactionManager extends TransactionManager
// 获取连接,开启事务,设置为手动管理事务 TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 提交事务 void commit(TransactionStatus var1) throws TransactionException;
// 回滚事务 void rollback(TransactionStatus var1) throws TransactionException; }
PlatformTransactionManager接口有很多实现类:
DataSourceTransactionManager是我们要重点关心的事务管理器实现类
doBegin() 会由获取一个连接Connection, 并设置为手动管理事务
doCommit() 方法 提交事务 connection.commit()
doRollback() 方法 回滚事务 connection.rollback()
Spring 事务管理底层原理:
11.4、使用Spring的注解声明事务管制
实验2:测试Spring的声明式事务
ApplicationContext.xml配置文件:
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 这里数据库连接池必须和访问数据库的连接池是同一个 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- (tx:annotation-driven表示代理 + 注解@transactional)组合使用 transaction-manager="transactionManager" 配置使用哪个事务管理器 transaction-manager属性的默认值是: transactionManager --> <tx:annotation-driven ></tx:annotation-driven>
在需要使用事务的方法上,添加注解 @Transactional
11.5、noRollbackFor和noRollbackForClassName测试不回滚的异常
实验3:noRollbackFor和noRollbackForClassName测试不回滚的异常
Spring默认情况下.是对RuntimeException 运行时异常.和它的子异常,进行事务回滚.
/** * @Transactional 表示当前方法加事务管理 <br/> * noRollbackFor = ArithmeticException.class 表示如果抛ArithmeticException则不回滚事务 <br/> * noRollbackForClassName表示指定的哪个全类名的异常不回滚事务<br/> */ @Transactional( noRollbackForClassName = "java.lang.ArithmeticException") public void multiUpdate() throws FileNotFoundException { userDao.updateUser(); int i = 12 / 0; bookDao.updateBook(); }
11.6、自定义设置回滚异常
实验5:rollbackFor和rollbackForClassName回滚的异常
/** * @Transactional 表示当前方法加事务管理 <br/> * noRollbackFor = ArithmeticException.class 表示如果抛ArithmeticException则不回滚事务 <br/> * noRollbackForClassName表示指定的哪个全类名的异常不回滚事务<br/> * rollbackFor指定哪些异常需要回滚事务 <br/> * rollbackForClassName指定哪些全类名的异常.需要回滚事务 */ @Transactional( rollbackForClassName = "java.io.FileNotFoundException" ) public void multiUpdate() throws FileNotFoundException { userDao.updateUser(); int i = 12; if (i == 12) { throw new FileNotFoundException("文件未找到异常,默认不回滚"); } bookDao.updateBook(); }
11.7、事务的只读属性
只读,是指,只能执行 select 查询操作,不允许执行 insert , update , delete 写操作( 如果执行写操作,抛异常 )
实验4:测试readOnly只读属性
/** * @Transactional 表示当前方法加事务管理 <br/> * readOnly设置是否只读,readOnly = true 表示 不允许写操作<br/> */ @Transactional( readOnly = true ) public void multiUpdate() throws FileNotFoundException { userDao.updateUser(); bookDao.updateBook(); }
11.8、事务超时属性timeout(秒为单位,了解内容)
/** * @Transactional 表示当前方法加事务管理 <br/> * <p> * timeout 属性设置几秒内不允许再执行sql语句 */ @Transactional(timeout = 3) public void multiUpdate() throws InterruptedException { userDao.updateUser(); Thread.sleep(4000); bookDao.updateBook(); }
事务超时的异常信息:
11.10、事务的传播特性propagation
什么是事务的传播行为 ( 传播特性又叫传播行为 ):
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
事务的传播特性,有以下几种类型:
11.11、注解演示事物传播特性
UserService
BookService
TransactionService
实验1:大小事务传播特性都是REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void multiTransaction() {
@Transactional(propagation = Propagation.REQUIRED)
public void updateBook() {
@Transactional(propagation=Propagation.REQUIRED)
public void updateUser() {
实验2:大小事务传播特性都是REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void multiTransaction()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateBook()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser()
实验3:大事务是REQUIRED,小1REQUIRED,小2REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRED)
public void multiTransaction()
@Transactional(propagation = Propagation.REQUIRED)
public void updateBook()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateUser()
11.9、事务隔离级别 ( 了解 )
数据库事务隔离级别,是为了解决数据库高并发过程中,事务与事务之间数据的安全问题.
数据库有四种事务隔离级别:
一:读未提交 read uncommitted
二:读已提交 read committed Oracle默认
三:可重复读 repeatable read mysql默认
四:串行事务 serializable (读写不并行)
由事务隔离级别产生的几个常见问题:
读未提交,可导致----->>>> 脏读
读已提交,可导致----->>>> 不可重复读
重复读,可导致 ----->>>> 幻读
什么是脏读?
脏读,就是查询到的数据,不是真实有效的数据.( 这个数据可能会随时被回滚 )
什么是不可重复读?
不可重复读,是指多次查询相同的数据.得到的结果不是完全一样.
什么是幻读 ?
幻读是指,多次查询相同的数据,得到的结果都跟第一次一样.( 但其实,查询的数据已经被修改[ 有插入,有修改,有删除 ] )
1.查看当前会话隔离级别
select @@tx_isolation;
2.查看系统当前隔离级别
select @@global.tx_isolation;
3.设置当前会话隔离级别
set session transaction isolation level read uncommitted; 设置读未提交
set session transaction isolation level read committed; 设置读已提交
set session transaction isolation level repeatable read; 设置可重复读
set session transaction isolation level serializable; 设置串行化
- 开启事务
start transaction;
5.提交事务
commit;
- 回滚事务
rollback;
11.9.1、读未提交导致脏读的演示:
11.9.2、读已提交导致不可重复读演示:
11.9.3、可重复读导致幻读的演示
11.9.4、串行化事务的演示
12、xml配置式事务声明
去掉。所有@Transactional的注解。
<!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 这里数据库连接池必须和访问数据库的连接池是同一个 --> <property name="dataSource" ref="dataSource" /> </bean> <!-- 事务属性 --> <tx:advice id="tx_advice"> <tx:attributes> <!-- 表示 udpateBook方法的传播特性是REQUIRES_NEW --> <tx:method name="updateBook" propagation="REQUIRES_NEW"/> <!-- 注意不要忘了multiUpdate和multiTransaction单独配置 --> <tx:method name="multiTransaction" propagation="REQUIRED"/> <!-- 以save打头的方法 --> <tx:method name="save*" propagation="REQUIRED"/> <tx:method name="insert*" propagation="REQUIRED"/> <tx:method name="update*" propagation="REQUIRED"/> <tx:method name="delete*" propagation="REQUIRED"/> <tx:method name="multi*" propagation="REQUIRED"/> <!-- 剩下的方法 设置只读 name="updateBook" 精确 name="save*" 半模糊 method name="*" 全模糊 匹配的规则是,越精确,越优先 --> <tx:method name="*" read-only="true"/> </tx:attributes> </tx:advice> <!-- 事务管理底层原理 : AOP代理 --> <aop:config> <aop:advisor advice-ref="tx_advice" pointcut="execution(public * com.demo..*Service*.*(..))" /> </aop:config>
- Spring整合Web
在web工程上使用Spring框架
13.1、在web工程中添加Spring的jar包。
Spring的核心包
spring-beans-4.0.0.RELEASE.jar
spring-context-4.0.0.RELEASE.jar
spring-core-4.0.0.RELEASE.jar
spring-expression-4.0.0.RELEASE.jar
aop包
spring-aop-4.0.0.RELEASE.jar
spring-aspects-4.0.0.RELEASE.jar
com.springsource.org.aopalliance-1.0.0.jar
com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
JDBC-ORM包
spring-jdbc-4.0.0.RELEASE.jar
spring-orm-4.0.0.RELEASE.jar
spring-tx-4.0.0.RELEASE.jar
Spring的web整合包
spring-web-4.0.0.RELEASE.jar
测试包
spring-test-4.0.0.RELEASE.jar
在web工程中使用Spring,需要做以下几点:
1 需要自己创建Spring的容器对象
2 需要我们自己管理Spring容器对象 ( Spring容器只有一个 )
3 需要很好的对Spring容器的创建和销毁做管理
整合Spring和Web容器分两个步骤:
1、导入spring-web-4.0.0.RELEASE.jar
2、在web.xml配置文件中配置org.springframework.web.context.ContextLoaderListener监听器监听ServletContext的初始化
3、在web.xml配置文件中配置contextConfigLocation上下文参数。配置Spring配置文件的位置,以用于初始化Spring容器
在web.xml中配置
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
ServletContextListener监听器用于监听ServletContext对象的创建和销毁
ServletContext对象在web工程启动的时候创建
ServletContext对象在web工程停止的时候销毁
获取WebApplicationContext上下文对象的方法如下:
方法一(推荐):
WebApplicationContextUtils.getWebApplicationContext(getServletContext())
方法二(不推荐):
getServletContext().getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);