只要是AOP的配置,都要导入aspectjweaver
jar包对应:
1、 spring的基础包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency>
2、 jdbcTemplate的包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.0.2.RELEASE</version> </dependency>
3、 spring事务管理的包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.0.2.RELEASE</version> </dependency>
4、 spring和junit整合的包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.0.2.RELEASE</version> </dependency>
5、 连接mysql数据库的jdbc包
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency>
6、 分析AOP中execution里的表达式的包
<dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.7</version> </dependency>
7、 测试类junit的包
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> </dependency>
协议:
IOC:
<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
https://www.springframework.org/schema/beans/spring-beans.xsd">
AOP:
<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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
事务管理:
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
第一天
第二天
第三天
第四天
前言
解耦
首先要先了解一个概念
耦合:程序间的依赖关系
包括:
类之间的依赖
方法间的依赖
解耦
降低程序中的依赖关系
实际开发中:
应该做到:编译期不依赖,运行时才依赖
解耦的思路:
第一步:使用反射来创建对象而避免使用new关键字
第二部:通过读取配置文件来获取要创建的对象全限定类名
DriverManager.registerDriver(new com.mysql.jdbc.Driver())
此时依赖jdbc的jar包
改成:Class.forName("com.mysql.jdbc.Driver")
此时还是不能运行,但是并不是编译错误,而是异常
Properties文件的读取方法:
//定义一个Properties对象
private static Properties props;
//实例化对象
props = new Properties();
//获取properties对象的流对象
InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(in);
Enumeration keys = props.keys();
while(keys.hasMoreElements()) {
//取出每个Key
try{
String key = keys.nextElement().toString();
String beanpath = props.getProperty(key);
Object value = Class.forName(beanpath).newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
throw new ExceptionInInitializerError("初始化properties失败");
}
ioc
ioc的作用:削减计算机程序的耦合(解除我们代码间的依赖关系)
控制反转:
从private IAccountDao accountDao = new AccountDaoImpl();
到private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
new的话主动权在app手里,而使用bean容器的话,你要什么是由我们自己的工厂决定。
这种被动接受的方式获取对象的思想就是控制反转,它是spring框架的核心之一。
1、首先需要添加jar包
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.2.RELEASE</version> </dependency>
2、导入约束。在官网下载spring的jar包,在文件位置docs/spring-framework-reference/index.html中打开,在左侧点击Core,按ctrl + f搜索xmlns,复制
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">`
到新建的bean.xml文件中,最后补全bean标签。
3、配置
<bean id = "accountService" class = "com.itheima.service.impl.AccountServiceImpl"></bean>
<bean id = "accountDao" class = "com.itheima.dao.impl.AccountDaoImpl"></bean>
4、打开spring ioc容器。
public class Client {
/**
* 获取spring的Ioc的核心容器,并根据id获取对像
* @param args
* ApplicationContext的三个常用实现类:
* ClassPathXmlApplicationContext:加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了(更常用)
* FileSyetemXmlApplicationContext:加载磁盘任意路径下的配置文件(必须又访问权限)
* AnnotationConfigApplicationContext:读取注解创建容器
*
* 核心容器的两个接口引发出的问题
* ApplicationContext: 单例对象适用
* 它在构建容器时,创建对象采取的策略是采用立即加载的方式,也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象
* BeanFactory: 多例对象适用
* 它在构建容器时,创建对象采取的策略是采用延迟加载的方式,也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象
*
*
*/
public static void main(String[] args) {
//1、获取核心容器对象
// ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
// ApplicationContext ac = new FileSystemXmlApplicationContext("");
//2、根据id获取bean对象
// IAccountService as = (IAccountService) ac.getBean("accountService");
// System.out.println(as);
// IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
// System.out.println(as);
//System.out.println(adao);
//as.saveAccount();
//----------BeanFactory----------//
Resource resource = new ClassPathResource("bean.xml");
BeanFactory factory = new XmlBeanFactory(resource);
IAccountService as = (IAccountService) factory.getBean("accountService");
System.out.println(as);
}
}
5、spring对bean的管理细节以及创建bean对象的三种方法:
<?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">
<!--把对象的创建交给spring来管理-->
<!--spring对bean的管理细节
1.创建bean的三种方式
2.bean对象的作用范围
3.bean对象的生命周期
-->
<!--创建Bean的三种方式 -->
<!-- 第一种方式:使用默认构造函数创建。
在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。
采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
-->
<!-- 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
<bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
<bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
-->
<!-- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
-->
<!-- bean的作用范围调整
bean标签的scope属性:
作用:用于指定bean的作用范围
取值: 常用的就是单例的和多例的
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="prototype"></bean>
-->
<!-- bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在,对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时spring框架为我们创建
活着:对象只要是在使用过程中就一直活着。
死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收
-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
scope="prototype" init-method="init" destroy-method="destroy"></bean>
</beans>
生命周期:
容器的close()方法是ClassPathXmlApplicationContext这个类的,但是上面的ClassPathXmlApplicationContext是实现了ApplicationContext的接口,而ApplicationContext没有close()方法,所以会报错,所以应该把ApplicationContext改成ClassPathXmlApplicationContext就不会报错了
依赖注入
6、依赖注入
依赖注入主要有两种方法:构造函数注入和set方法注入,各有弊端
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- spring依赖注入
依赖注入:
Dependency Injection
IOC的作用:
降低程序间的耦合(关系)
依赖关系的感觉案例:
以后都交给spring来维护
在当前类需要用到其他类的对象,由spring为我们提供,我们只需要在配置文件中说明依赖关系的维护;
依赖关系的维护:
就称之为依赖注入
依赖注入:
能注入的数据:有三类
基本类型和String
其他bean类型(在配置文件中或者注解配置过的bean)
复杂类型/集合类型
注入的方式:有三种
第一种:使用构造函数提供
第二种:使用set方法提供
第三种:使用注解提供
-->
<!-- 构造函数注入:
使用的标签:constructor-arg
标签出现的位置:bean标签的内部
标签中的属性:
type:用于指定要注入的数据的数据类型,同时该数据类型也是构造函数中某个或某些参数的类型(即将构造函数中所有为type类型的参数全部注入value)
index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值。索引的位置从0开始。
name:用于指定给构造函数中指定名称的参数赋值(常用的)
==================以上三个用于指定给构造函数中哪个参数赋值===========================
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在Spring的Ioc核心容器中出现过的bean对象
优势:
在获取对象时,注入数据是必须的操作,否则对象无法创建成功
弊端:
改变了bean对象的实例化方式,使我们在创建对象时如果用不到这些数据也必须提供
-->
<bean id = "accountService" class = "com.itheima.service.impl.AccountServiceImpl">
<constructor-arg name="name" value="杜芸菲"></constructor-arg>
<constructor-arg name="age" value="18"></constructor-arg>
<constructor-arg name="brithday" ref="now"></constructor-arg>
</bean>
<!-- 配置一个日期对象 -->
<bean id="now" class="java.util.Date"></bean>
<!-- set方法注入 (更常用)
涉及的标签:property
出现的位置:bean标签的内部
标签的属性:
name:用于指定注入时所调用的set方法名称
value:用于提供基本类型和String类型的数据
ref:用于指定其他的bean类型数据。它指的就是在Spring的Ioc核心容器中出现过的bean对象
优势:
创建对象时没有明确的限制,可以直接使用默认构造函数
弊端:
如果有某个成员必须有值,则获取对象时有可能set方法没有执行。
-->
<bean id = "accountService2" class = "com.itheima.service.impl.AccountServiceImpl2">
<property name="name" value="321"></property>
<property name="age" value="21"></property>
<property name="brithday" ref="now"></property>
</bean>
<!-- 复杂类型的注入/集合类型的注入
用于给List结构集合注入的标签有
list array set
用于给Map结构集合注入的标签有
map props
结构相同,标签可以互换
-->
<bean id = "accountService3" class = "com.itheima.service.impl.AccountServiceImpl3">
<property name="mystrs">
<array>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</array>
</property>
<property name="myList">
<list>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</list>
</property>
<property name="mySet">
<set>
<value>AAA</value>
<value>BBB</value>
<value>CCC</value>
</set>
</property>
<property name="myMap">
<map>
<entry key="testA" value="aaa"></entry>
<entry key="testB">
<value>BBB</value>
</entry>
</map>
</property>
<property name="myProp">
<props>
<prop key="testC">ccc</prop>
<prop key="testB">bbb</prop>
</props>
</property>
</bean>
</beans>
spring中的常用注解
基于注解的ioc配置
首先,需要在xml文件里导入标签扫描包。由于扫描包的标签不是在beans的约束中,而是一个名称为context名称空间和约束中,所以要重新导约束,就是要加几条context的约束。
基于注解的ioc配置
首先,需要在xml文件里导入标签扫描包。由于扫描包的标签不是在beans的约束中,而是一个名称为context名称空间和约束中,所以要重新导约束,就是要加几条context的约束。
<?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
http://www.springframework.org/schema/context/spring-context.xsd">
<!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名称为
context名称空间和约束中-->
<context:component-scan base-package="com.itheima"></context:component-scan>
</beans>
package com.itheima.service.impl;
import com.itheima.dao.IAccountDao;
import com.itheima.service.IAccountService;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
/**
* 账户的业务层实现类
*
* 曾经XML的配置:
* <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"
* scope="" init-method="" destroy-method="">
* <property name="" value="" | ref=""></property>
* </bean>
*
* 用于创建对象的
* 他们的作用就和在XML配置文件中编写一个<bean>标签实现的功能是一样的
* Component:
* 作用:用于把当前类对象存入spring容器中
* 属性:
* value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。
* Controller:一般用在表现层
* Service:一般用在业务层
* Repository:一般用在持久层
* 以上三个注解他们的作用和属性与Component是一模一样。
* 他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰
*
*
* 用于注入数据的
* 他们的作用就和在xml配置文件中的bean标签中写一个<property>标签的作用是一样的
* Autowired:
* 作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
* 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
* 如果Ioc容器中有多个类型匹配时:
* 出现位置:
* 可以是变量上,也可以是方法上
* 细节:
* 在使用注解注入时,set方法就不是必须的了。
* Qualifier:
* 作用:在按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后我们讲)
* 属性:
* value:用于指定注入bean的id。
* Resource
* 作用:直接按照bean的id注入。它可以独立使用
* 属性:
* name:用于指定bean的id。
* 以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
* 另外,集合类型的注入只能通过XML来实现。
*
* Value
* 作用:用于注入基本类型和String类型的数据
* 属性:
* value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式)
* SpEL的写法:${表达式}
*
* 用于改变作用范围的
* 他们的作用就和在bean标签中使用scope属性实现的功能是一样的
* Scope
* 作用:用于指定bean的作用范围
* 属性:
* value:指定范围的取值。常用取值:singleton prototype
*
* 和生命周期相关 了解
* 他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的
* PreDestroy
* 作用:用于指定销毁方法
* PostConstruct
* 作用:用于指定初始化方法
*/
@Service("accountService")
//@Scope("prototype")
public class AccountServiceImpl implements IAccountService {
// @Autowired
// @Qualifier("accountDao1")
@Resource(name = "accountDao2")
private IAccountDao accountDao = null;
@PostConstruct
public void init(){
System.out.println("初始化方法执行了");
}
@PreDestroy
public void destroy(){
System.out.println("销毁方法执行了");
}
public void saveAccount(){
accountDao.saveAccount();
}
}
@Autowired标签:首先按照被autowired的对象,在ioc容器中查找相同类型的对象,如果只有一个,就直接配置,如果不少于1个,就再按照对象名继续筛选匹配,如果没有跟对象名相同的bean对象,就报错,这样会很麻烦,这时候就需要用到Qualifier,它可以在Autowired的基础上进行按照id筛选。
@Resource标签等于@Autowired + @Qualifier,更方便
案例使用xml方式和注解方式实现单表的CRUD操作
先导入jar包,首先需要用到spring-context,因为要用到ioc容器。
其次需要dbutils,因为需要QueryRunner。
然后需要mysql-connector-java,连接mysql数据库的jar包
然后需要c3p0,因为ComboPooledDataSource dataSource= new ComboPooledDataSource(),这里的ComboPooledDataSource 是c3p0里的。
dbutils用法:
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
// dataSource.setJdbcUrl("jdbc:mysql:///test01");
// 如果在连接mysql8.x的时候,报server Time zone ?????
dataSource.setJdbcUrl( "jdbc:mysql://localhost:3306/test01?useSSL=false&serverTimezone=UTC");
dataSource.setUser("laodu1");
dataSource.setPassword("123456");
配置好dateSource后
QueryRunner runner = new QueryRunner(DBUtil.getDataSource());
改造基于注解的ioc案例,使用纯注解的方式实现
这里的内容主要是如何把xml文件彻底删除。
这里需要用到一些新注解Configuration, ComponentScan,Bean, Import, PropertySource:
* 该类是一个配置类,它的作用和bean.xml是一样的
* spring中的新注解
* Configuration
* 作用:指定当前类是一个配置类
* 细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
* ComponentScan
* 作用:用于通过注解指定spring在创建容器时要扫描的包
* 属性:
* value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
* 我们使用此注解就等同于在xml中配置了:
* <context:component-scan base-package="com.itheima"></context:component-scan>
* Bean
* 作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
* 属性:
* name:用于指定bean的id。当不写时,默认值是当前方法的名称
* 细节:
* 当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。
* 查找的方式和Autowired注解的作用是一样的
* Import
* 作用:用于导入其他的配置类
* 属性:
* value:用于指定其他配置类的字节码。
* 当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
* PropertySource
* 作用:用于指定properties文件的位置
* 属性:
* value:指定文件的名称和路径。
* 关键字:classpath,表示类路径下
之前的用的解析配置文件都是这样的:ApplicationContext ac = new ClassPathXmlApplicationContext(“bean.xml”);
但是现在bean.xml文件已经被删除了,这样配置就不行了。所以这时要用到另一个实现类:AnnotationConfigApplicationContext
单击 ApplicationContext, 点击show Diagram,再点击show Implementations里就能找到AnnotationConfigApplicationContext。 他能解析被@Configuration注解过的类,不过当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
和xml文件相同,首先需要Configuration来给主配置文件注解,表示这是配置类,然后还需要加入@ComponentScan,来表示需要扫描注解的包。
//@Configuration
@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
}
然后测试类:
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
注意,这里直接把SpringConfiguration当成参数传进去,所以SpringConfiguration的@Configration可以不写。
上面是只有一个配置类的情况。当我们的配置类有很多,有多种方法可以实现。
一、
1、给其它配置类加入@Configration注释
2、在主配置文件的注释@ComponentScan({“com.itheima”,””,””})中加入需要另外解析的配置文件的包
3、 ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);就可以实现
二、
ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfiguration.class),直接在解析的时候把格外需要解析的类加进去。
并且这些类也不用写@Configration注解
三、最好的方式
在主配置文件中,使用@Import注解:@Import(JdbcConfig.class),JdbcConfig也不需要写@Configration,在解析时也只需要写主配置文件就行了,比上面两个都轻松很多。ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
对于配置类中的细节
像这种把参数写死的,早都不这么用了。这时就可以使用标签@PropertySource,然后在类中加入属性,注意@PropertySource中classpath:不能省,表示类路径下,并且里面的包分级是用/不是用.
这样整合在一起就可以直接用了,也便于后期的修改。
spring和Junit整合
注意: 当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上 在测试方法中,由于每个方法都要写
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
这样重发代码块太多了,于是想到了这个方法:
@Before
public void init() {
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
IAccountService accountService = ac.getBean("accountService", IAccountService.class);
}
这样就可以在每个方法运行前,先使用这个init的方法,但是测试程序员不一定会用spring,所以这样不行,于是就联想到了spring和junit的整合。
-
使用Junit单元测试:测试我们的配置
-
Spring整合junit的配置
1、导入spring整合junit的jar(坐标)
2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
@Runwith
3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
@ContextConfiguration
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在地位置当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
*/
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
整合了的测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
@Autowired
private IAccountService as = null;
@Test
public void testFindAll() {
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
Account account = new Account();
account.setName("test anno");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//3.执行方法
as.deleteAccount(4);
}
}
完善我们的account案例
现在加入一个转账的方法:
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
// int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
}
这样运行是没问题的,但是这是个假象。当我们把int i=1/0的注释去掉之后,就会不满足事务的一致性,也就是会出现一方已经扣钱了,而另一方没有加钱的情况。
这里就需要用到ThreadLocal,也就是给一个线程绑定一个连接。
先创建一个线程绑定的类,这里的DataSource可以用之前bean.xml中配置ComboPooledDataSource的bean对象来注入。因为ComboPooledDataSource就是DataSource的子类。
先获取一个线程,然后看看线程上是否有连接,如果没有,就把当前获取的连接给注入进去,这样这个连接就被绑定住了。
public class ConnectionUtils {
private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
/**
* 获取当前线程上的连接
* @return
*/
public Connection getThreadConnection() {
try{
//1.先从ThreadLocal上获取
Connection conn = tl.get();
//2.判断当前线程上是否有连接
if (conn == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
conn = dataSource.getConnection();
tl.set(conn);
}
//4.返回当前线程上的连接
return conn;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
}
接下来就能做事务管理的操作了。先在刚才创的类中获取那个绑定了线程的连接,之后用commit和rollback。这里要注意了。connectionUtils.getThreadConnection().close()只是把线程给关闭了,也就是把连接还回连接池中,但是线程还没还回线程池,所以要加入tl.remove();这一步将线程还回去。
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 开启事务
*/
public void beginTransaction(){
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 提交事务
*/
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 回滚事务
*/
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 释放连接
*/
public void release(){
try {
connectionUtils.getThreadConnection().close();//还回连接池中
connectionUtils.removeConnection();
}catch (Exception e){
e.printStackTrace();
}
}
}
这样绑定连接的事务操作的类就做好了。
但是要注意,之前的QueryRunner用的还是自己创建的连接,所以我们要换成这种绑定了线程的连接,于是:
return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
在sql语句前加入连接,可以让QueryRunner用指定连接。
然后再bean.xml文件中,把对QueryRunner的依赖注入给去掉。
这里的ConnectionUtils必须得是单例。这样再TransactionManager和AccountDaoImpl中用的就是一个ConnectionUtils。当TransactionManager绑定连接到线程以后,AccountDaoImpl再用这个连接去做事务。
统一事务后的transfer:
@Override
public void transfer(String sourceName, String targetName, Float money) {
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
int i=1/0;
//2.6更新转入账户
accountDao.updateAccount(target);
//3.提交事务
txManager.commit();
}catch (Exception e){
//4.回滚操作
txManager.rollback();
e.printStackTrace();
}finally {
//5.释放连接
txManager.release();
}
回顾之前讲过的一个技术:动态代理
现在设想一个情景,有一个厂商,卖电脑1w块钱一台,但是这个厂商找了个代理,代理抽了2000差价,于是厂商一台电脑只能收到8成的钱。现在我们就要做一个这样的代理。
一、基于接口的动态代理
最大的优势:不修改源码的基础上增强
但是如果被增强的类没有接口,就不能增强
注意:匿名内部类访问成员变量需要用final修饰成员变量,以下两种的两个product就是被代理对象都要被final修饰才行。如果需要被final修饰的也被private修饰了的话,在它的set方法上加final也行。
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于接口的动态代理:
* 涉及的类:Proxy
* 提供者:JDK官方
* 如何创建代理对象:
* 使用Proxy类中的newProxyInstance方法
* 创建代理对象的要求:
* 被代理类最少实现一个接口,如果没有则不能使用
* newProxyInstance方法的参数:
* ClassLoader:类加载器
* 它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
* Class[]:字节码数组
* 它是用于让代理对象和被代理对象有相同方法。固定写法。
* InvocationHandler:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
代理实现代码
public static void main(String[] args) {
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 作用:执行被代理对象的任何接口方法都会经过该方法
* 方法参数的含义
* @param proxy 代理对象的引用
* @param method 当前执行的方法
* @param args 当前执行方法所需的参数
* @return 和被代理对象方法有相同的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
proxyProducer.saleProduct(10000f);
}
二、基于子类的动态代理
需要导入第三方jar包
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
* 动态代理:
* 特点:字节码随用随创建,随用随加载
* 作用:不修改源码的基础上对方法增强
* 分类:
* 基于接口的动态代理
* 基于子类的动态代理
* 基于子类的动态代理:
* 涉及的类:Enhancer
* 提供者:第三方cglib库
* 如何创建代理对象:
* 使用Enhancer类中的create方法
* 创建代理对象的要求:
* 被代理类不能是最终类
* create方法的参数:
* Class:字节码
* 它是用于指定被代理对象的字节码。
*
* Callback:用于提供增强的代码
* 它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
* 此接口的实现类都是谁用谁写。
* 我们一般写的都是该接口的子接口实现类:MethodInterceptor
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 执行北地阿里对象的任何方法都会经过该方法
* @param proxy
* @param method
* @param args
* 以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
* @param methodProxy :当前执行方法的代理对象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
//提供增强的代码
Object returnValue = null;
//1.获取方法执行的参数
Float money = (Float)args[0];
//2.判断当前方法是不是销售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money*0.8f);
}
return returnValue;
}
});
cglibProducer.saleProduct(12000f);
}
解决案例中的问题
那么之前的transfer方法何尝不能也用动态代理来写呢。
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
/**
* 获取Service代理对象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事务的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if("test".equals(method.getName())){
return method.invoke(accountService,args);
}
Object rtValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
rtValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return rtValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
在bean.xml文件里加入就可以用了。
<bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>
AOP概念
AOP的相关术语
连接点就是被代理对象类中所有的方法。
切入点是被代理对象中被增强的方法,因为不是所有方法都会被增强。所有所有的切入点都是连接点,但是不是所有的连接点都是切入点。
**spring中基于XML和注解AOP配置**
一、基于XML
首先导入依赖jar包:
这里的aspectjweaver的作用是解析配置文件中execution()里的表达式
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
导入AOP协议
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
编写配置文件:
<?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
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 配置srping的Ioc,把service对象配置进来-->
<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>
<!--spring中基于XML的AOP配置步骤
1、把通知Bean也交给spring来管理
2、使用aop:config标签表明开始AOP的配置
3、使用aop:aspect标签表明配置切面
id属性:是给切面提供一个唯一标识
ref属性:是指定通知类bean的Id。
4、在aop:aspect标签的内部使用对应标签来配置通知的类型
我们现在示例是让printLog方法在切入点方法执行之前执行:所以是前置通知
aop:before:表示配置前置通知
method属性:用于指定Logger类中哪个方法是前置通知
pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
切入点表达式的写法:
关键字:execution(表达式)
表达式:
访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表)
标准的表达式写法:
public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
访问修饰符可以省略
void com.itheima.service.impl.AccountServiceImpl.saveAccount()
返回值可以使用通配符,表示任意返回值
* com.itheima.service.impl.AccountServiceImpl.saveAccount()
包名可以使用通配符,表示任意包。但是有几级包,就需要写几个*.
* *.*.*.*.AccountServiceImpl.saveAccount())
包名可以使用..表示当前包及其子包
* *..AccountServiceImpl.saveAccount()
类名和方法名都可以使用*来实现通配
* *..*.*()
参数列表:
可以直接写数据类型:
基本类型直接写名称 int
引用类型写包名.类名的方式 java.lang.String
可以使用通配符表示任意类型,但是必须有参数
可以使用..表示有无参数均可,有参数可以是任意类型
全通配写法:
* *..*.*(..)
实际开发中切入点表达式的通常写法:
切到业务层实现类下的所有方法
* com.itheima.service.impl.*.*(..)
-->
<!-- 配置Logger类 -->
<bean id="logger" class="com.itheima.utils.Logger"></bean>
<!--配置AOP-->
<aop:config>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
<!-- <aop:before method="printLog" pointcut="execution(* com.itheima.service.impl.*.*(..))"></aop:before>-->
<aop:before method="printLog" pointcut="execution(* *..* (..))"></aop:before>
</aop:aspect>
</aop:config>
</beans>
上面的配置只有前置通知的,剩下三个通知基本也是一样的:
配置前置通知:在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut ="execution(* com.itheima.service.impl.*.*(..))" ></aop:before>
配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
<aop:after-returning method="afterReturningPrintLog" pointcut ="execution(* com.itheima.service.impl.*.*(..))"></aop:after-returning>
配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
<aop:after-throwing method="afterThrowingPrintLog" pointcut ="execution(* com.itheima.service.impl.*.*(..))"></aop:after-throwing>
配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
<aop:after method="afterPrintLog" pointcut ="execution(* com.itheima.service.impl.*.*(..))"></aop:after>
但是可以发现,每个通知都写一遍execution表达式很麻烦,所以下面就来通配它们
加入标签<aop:pointcut id=”” expression=””></aop:pointcut>,然后再方法中将pointcut变成pointcur-ref来根据Id使用。需要注意的是,当aop:pointcur标签写在切面外面时,必须写在切面配置的上面,这时协议里的规范,不然回报错。
<!--配置AOP-->
<aop:config>
<!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
此标签写在aop:aspect标签内部只能当前切面使用。
它还可以写在aop:aspect外面,此时就变成了所有切面可用
-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--配置切面 -->
<aop:aspect id="logAdvice" ref="logger">
<!-- 配置前置通知:在切入点方法执行之前执行
<aop:before method="beforePrintLog" pointcut-ref="pt1" ></aop:before>-->
<!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个
<aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>-->
<!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
<aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>-->
<!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行
<aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>-->
</aop:aspect>
</aop:config>
环境通知
环绕通知
问题:
当我们配置了环绕通知之后,切入点方法没有执行,而通知方法执行了。
分析:
通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而我们的代码中没有。
解决:
Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法proceed(),此方法就相当于明确调用切入点方法。
该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
spring中的环绕通知:
它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
public Object aroundPringLog(ProceedingJoinPoint pjp){
Object rtValue = null;
try{
Object[] args = pjp.getArgs();//得到方法执行所需的参数
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");
rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");
return rtValue;
//这里必须要用Throwable, Exception拦不住
}catch (Throwable t){
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
throw new RuntimeException(t);
}finally {
System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
}
}
二、基于注解(推荐环绕通知)
注意:spring在注解上用四个通知,会有顺序不对的情况,请慎重使用,不过现在的新版本还有没有这问题就不清楚了。
首先导入注解需要的context协议 和 添加扫描包(创建容器的和开启AOP注解支持的)
<?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"
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/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时要扫描的包-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!-- 配置spring开启注解AOP的支持 -->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
加入新的注解:
@Aspect:表示当前类是一个切面类
@Before(“pt1()”):前置通知
@AfterReturning(“pt1()”): 后置通知
@AfterThrowing(“pt1()”) : 异常通知
@After(“pt1()”) : 最终通知
@Around(“pt1()”) : 环绕通知
切入点表达式配置方法:
@Pointcut("execution(* com.itheima.service.impl.*.*(..))") private void pt1(){}
注意,使用的时候pt1()的括号不能省略
spring中的JdbcTemplate
需要新导入上面图片中的spring-jdbc和spring-tx的jar包
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
</dependencies>
先来看看jdbcTemplate最简单的用法
//准备数据源:spring的内置数据源
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/eesy");
ds.setUsername("root");
ds.setPassword("1234");
//1.创建JdbcTemplate对象
JdbcTemplate jt = new JdbcTemplate();
//给jt设置数据源
jt.setDataSource(ds);
//2.执行操作
jt.execute("insert into account(name,money)values('ccc',1000)");
这里看到很多的new和数据库的信息都被写死了,可以用ioc的容器来优化。
这是query定位的两个方法和两个方法的区别,两个我们都能用,他们只有版本的差异。
下面是jdbcTemplate的CRUD操作:
public class JdbcTemplateDemo3 {
public static void main(String[] args) {
//1.获取容器
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.获取对象
JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
//3.执行操作
//保存
// jt.update("insert into account(name,money)values(?,?)","eee",3333f);
//更新
// jt.update("update account set name=?,money=? where id=?","test",4567,7);
//删除
// jt.update("delete from account where id=?",8);
//查询所有
// List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);
// List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
// for(Account account : accounts){
// System.out.println(account);
// }
//查询一个
// List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
// System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));
//查询返回一行一列(使用聚合函数,但不加group by子句)
Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f);
System.out.println(count);
}
}
/**
* 定义Account的封装策略
*/
class AccountRowMapper implements RowMapper<Account>{
/**
* 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
* @param rs
* @param rowNum
* @return
* @throws SQLException
*/
@Override
public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
Account account = new Account();
account.setId(rs.getInt("id"));
account.setName(rs.getString("name"));
account.setMoney(rs.getFloat("money"));
return account;
}
}
当我们有多个dao对象时,在每一个dao都会有下面这个重复代码块
所以为了抽取这些代码块,我们创建了一个父类对象,让所有的dao来继承它
此类用于抽取dao中的重复代码
public class JdbcDaoSupport {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public JdbcTemplate getJdbcTemplate() {
return jdbcTemplate;
}
public void setDataSource(DataSource dataSource) {
if(jdbcTemplate == null){
jdbcTemplate = createJdbcTemplate(dataSource);
}
}
private JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
}
然后在创建dao的bean对象时,加入dataSource的properties调用父类的setDataSource来创建JdbcTemplate。
这样就成功了抽取了重复代码块,但是这些spring都有想到,当我们把父类去掉后,发现dao自动就为我们导入了JdbcDaoSupport,这是spring为我们实现的。
并且当我们打开源码看,发现里面也是有setDataSource的方法,和上面创建的父类的实现方式是差不多的,所以
这是xml的配置方式,xml是通过继承JdbcDaoSupport来实现,而注解则是通过加@Autowired来实现,但是需要注意的是,当我们继承了JdbcDapSupport了,就不能再对JdbcTemplate使用注释了,因为在上图可以看见@Nullable,是不能加注释的。
spring基于AOP的事务控制
这一段其实是之前的transfer操作,有用动态代理做过,但是还没有用AOP实现过,这个可以直接看day04-02和day04-03
spring中的事务控制(基于xml和注解的)
在之前的转账操作中,我们为了事务的统一性,先将连接绑定到线程再进行操作,需要自己写实现方式,但是这些其实spring都为我们考虑到了,实现这个的过程就是今天要学的声明式事务控制。需要注意的是,spring的事务控制都是基于AOP的,而且接口都在spring-tx中,需要导入jar包和协议
事务控制分成声明式和编程式,我们主要是会用声明式。
这里我们主要使用DataSourceTransactionManager
TransactionDefinition是事务的定义信息
隔离级别默认是数据库的隔离级别
事务的传播行为
REQUIRED:增删改的选择
SOPPORTS :查询的选择
下面开始正题:声明式事务的配置步骤:
XML :
<!-- spring中基于XML的声明式事务控制配置步骤
1、配置事务管理器
2、配置事务的通知
此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
使用tx:advice标签配置事务通知
属性:
id:给事务通知起一个唯一标识
transaction-manager:给事务通知提供一个事务管理器引用
3、配置AOP中的通用切入点表达式
4、建立事务通知和切入点表达式的对应关系
5、配置事务的属性
是在事务的通知tx:advice标签的内部
-->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置事务的属性
isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
read-only:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
-->
<tx:attributes>
这里的*号表示切入点表达式里所有的方法
<tx:method name="*" propagation="REQUIRED" read-only="false"/>
这里的find*表示切入点表达式里所有的方法中以find开头的。
<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
</tx:advice>
<!-- 配置aop-->
<aop:config>
<!-- 配置切入点表达式-->
<aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut>
<!--建立切入点表达式和事务通知的对应关系 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
</aop:config>
需要导入的协议:
<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
注解:
首先就是在XML的基础上要加上context的协议。
xmlns:context=“http://www.springframework.org/schema/context”
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
<!-- spring中基于注解 的声明式事务控制配置步骤
1、配置事务管理器
2、开启spring对注解事务的支持
3、在需要事务支持的地方使用@Transactional注解
-->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
在需要事务支持的地方使用@Transactional注解,,这里的Transactional里面包含了很多的属性,但是如果定义在类名前,就对所有的方法都采用一样的属性,而在前面讲了查询和增删改是不一样的,所以当我们单独对里面的一个方法再用Transactional定义时,虽然会覆盖掉类名前的配置,但是如果我们有很多的方法,岂不是要每个都单独配一遍,这样会相当麻烦,所有推荐xml来配置声明式。
当我们用注解时,就不能再继承Transaction类了,改用@Autowired,然后再bean文件中给jdbcTemplate注入dataSource
基于纯注解的声明式事务控制
创建主配置文件:
@Configuration
@ComponentScan("com.itheima")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement
public class SpringConfiguration {
}
创建JdbcConfig
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/**
* 创建JdbcTemplate
* @param dataSource
* @return
*/
@Bean(name="jdbcTemplate")
public JdbcTemplate createJdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
/**
* 创建数据源对象
* @return
*/
@Bean(name="dataSource")
public DataSource createDataSource(){
DriverManagerDataSource ds = new DriverManagerDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
}
创建TranscationConfig
public class TransactionConfig {
/**
* 用于创建事务管理器对象
* @param dataSource
* @return
*/
@Bean(name="transactionManager")
public PlatformTransactionManager createTransactionManager(DataSource dataSource){
return new DataSourceTransactionManager(dataSource);
}
}
这里注意,在spring-test和junit的整合中,原本的
@ContextConfiguration(locations = “classpath:bean.xml”)
要变成
@ContextConfiguration(classes= SpringConfiguration.class)
最后,结束啦!!!