1 Spring简介
1.1 Spring是什么
Spring是分层的JavaSE/EE应用full-stack(全栈:各层都有对应的解决方案)轻量级开源框架,以IOC(Inverse of Control:控制反转Bean的创建权)和AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了表现层SpringMVC和持久层Spring JDBCTemplate以及业务层事务管理等众多企业级应用技术,还能整合开源世界众多著名的第三方框架和类库
1.2 Spring的优势
(1)方便解耦,简化开发
通过Spring提供的IoC容器,可以将对象间的依赖关系交由Spring进行控制,避免硬编码造成的过度耦合,用户也不必再为单例模式类、属性文件解析等底层的需求编写代码,可以更专注于上层的应用
(2)AOP编程支持
通过Spring的AOP功能,方便进行面向切面编程,许多不能用传统的OOP实现的功能用AOP轻松实现
(3)声明式事务的支持
减少事务管理代码的编写,通过声明式灵活的进行事务管理,提高开发效率,通过配置的方式一次性配置一片方法都可以自动进行事务控制
(4)方便程序的测试
集成Junit
(5)方便集成各种优秀框架
(6)降低JavaEE API的使用难度
Spring对JavaEE API(如JDBC、JavaMail、远程调用等)进行相应的封装,形成模板,大大降低了使用难度
(7)Java源码经典学习范例
1.3 Spring的体系结构
Core Cotainer:Beans Core Context SpEL,之后使用Spring要导入的jar包
2 Spring快速入门
2.1 Spring程序开发步骤
-
导入Spring开发的基本包坐标
pom.xml中Spring依赖坐标
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>ioc</artifactId> <version>1.0-SNAPSHOT</version> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.0.5.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency> </dependencies> </project>
-
编写Dao接口及实现类
-
创建Spring核心配置文件
-
在Spring配置文件中配置UserDaoImpl
-
使用Spring的API获得Bean实例:Spring通过Bean的无参构造创建Bean
3 Spring配置文件
3.1 Bean标签基本配置
用于配置对象交由Spring来创建
默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功
基本属性:
- id:Bean实例在Spring容器中的唯一标识
- class:Bean的全限定名称
3.2 Bean标签的范围配置
scope属性:指对象的作用范围,取值如下:
- singleton:默认值,单例的
- prototype:多例的
- request:WEB项目中,Spring创建一个Bean对象,将对象存入到request域中
- session:WEB项目中,Spring创建一个Bean对象,将对象存入到session域中
- global session:WEB项目中,应用在Portlet环境,如果没有Protlet环境那么global session相当于session
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.wds.dao.impl.UserDaoImpl" scope="singleton"/>
</beans>
当bean的作用范围scope为singleton时,创建的对象为同一个对象,并且地址相同,说明Spring容器当中存在一个userDao对象,在创建Spring容器时,创建Bean对象
@Test
public void test1(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml"); // 创建Spring容器
UserDao userDao1 = (UserDao) app.getBean("userDao");
UserDao userDao2 = (UserDao) app.getBean("userDao");
System.out.println(userDao1); // com.wds.dao.impl.UserDaoImpl@3043fe0e
System.out.println(userDao2); // com.wds.dao.impl.UserDaoImpl@3043fe0e
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userDao" class="com.wds.dao.impl.UserDaoImpl" scope="prototype"/>
</beans>
当bean的作用范围scope为prototyp时,创建的对象为不同对象,并且地址不同,说明Spring容器当中存在多个userDao对象,在每次getBean时创建Bean对象
@Test
public void test1(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao1 = (UserDao) app.getBean("userDao");
UserDao userDao2 = (UserDao) app.getBean("userDao");
System.out.println(userDao1); // com.wds.dao.impl.UserDaoImpl@1563da5
System.out.println(userDao2); // com.wds.dao.impl.UserDaoImpl@2bbf4b8b
}
小结:
-
当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心配置文件被加载时,实例化配置的Bean实例
Bean的生命周期:
- 对象创建:当应用加载,创建容器时,对象就被创建了
- 对象运行:只要容器在,对象就一直活着
- 对象销毁:当应用卸载,销毁容器时,对象就被销毁了
-
当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时,实例化配置的Bean实例
Bean的生命周期:
- 对象创建:当使用对象时,创建新的对象实例
- 对象运行:只要对象在使用中,就一直活着
- 对象销毁:当对象长时间不用时,被JAVA的垃圾回收器回收
3.3 Bean生命周期配置
- init-method:指定类中的初始化方法名称
- destroy-method:指定类中销毁方法名称
UserDaoImpl类的相关方法
public class UserDaoImpl implements UserDao {
public UserDaoImpl() {
System.out.println("UserDaoImpl创建....");
}
public void init(){
System.out.println("初始化方法....");
}
public void destory(){
System.out.println("销毁方法....");
}
public void save() {
System.out.println("save running ....");
}
}
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 id="userDao" class="com.wds.dao.impl.UserDaoImpl" init-method="init" destroy-method="destory"/>
</beans>
测试方法及结果
@Test
public void test1(){
ApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
UserDao userDao = (UserDao) app.getBean("userDao");
System.out.println(userDao);
// 手动关闭Spring容器
((ClassPathXmlApplicationContext) app).close();
}
/*
UserDaoImpl创建....
初始化方法....
com.wds.dao.impl.UserDaoImpl@bd8db5a
六月 22, 2021 9:50:18 上午 org.springframework.context.support.AbstractApplicationContext doClose
信息: Closing org.springframework.context.support.ClassPathXmlApplicationContext@31b7dea0: startup date [Tue Jun 22 09:50:18 CST 2021]; root of context hierarchy
销毁方法....
*/
3.4 Bean实例化的三种方式
-
无参构造方法实例化
-
工厂静态方法实例化
创建静态工厂及相关方法
public class StaticFactory { public static UserDao getUserDao(){ return new UserDaoImpl(); } }
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 id="userDao" class="com.wds.factory.StaticFactory" factory-method="getUserDao"/> </beans>
-
工厂实例方法实例化
创建动态工厂及相关方法
public class DynamicFactory { public UserDao getUserDao(){ return new UserDaoImpl(); } }
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 id="factory" class="com.wds.factory.DynamicFactory"/> <bean id="userDao" factory-bean="factory" factory-method="getUserDao"/> </beans>
3.5 Bean依赖注入的数据类型
-
普通数据类型
-
引用数据类型
-
集合数据类型
public class UserDaoImpl implements UserDao { private List<String> strList; private Map<String, User> userMap; private Properties properties; private String username; private int age; public void setStrList(List<String> strList) { this.strList = strList; } public void setUserMap(Map<String, User> userMap) { this.userMap = userMap; } public void setProperties(Properties properties) { this.properties = properties; } public void setUsername(String username) { this.username = username; } public void setAge(int age) { this.age = age; } public void save() { System.out.println(username + "=====" + age); System.out.println(strList); System.out.println(userMap); System.out.println(properties); System.out.println("save running ...."); /* 打印的结果: zhangsan=====18 [aaa, bbb, ccc] {user1=User{name='zhangsan', addr='河北'}, user2=User{name='lisi', addr='北京'}} {p3=ppp3, p2=ppp2, p1=ppp1} save running .... */ } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="user1" class="com.wds.domain.User"> <property name="name" value="zhangsan"/> <property name="addr" value="河北"/> </bean> <bean id="user2" class="com.wds.domain.User"> <property name="name" value="lisi"/> <property name="addr" value="北京"/> </bean> <bean id="userDao" class="com.wds.dao.impl.UserDaoImpl"> <property name="username" value="zhangsan"/> <property name="age" value="18"/> <property name="strList"> <list> <value>aaa</value> <value>bbb</value> <value>ccc</value> </list> </property> <property name="userMap"> <map> <entry key="user1" value-ref="user1"/> <entry key="user2" value-ref="user2"/> </map> </property> <property name="properties"> <props> <prop key="p1">ppp1</prop> <prop key="p2">ppp2</prop> <prop key="p3">ppp3</prop> </props> </property> </bean> <bean id="userService" class="com.wds.service.impl.UserServiceImpl"> <constructor-arg name="userDao" ref="userDao"/> </bean> </beans>
3.6 引入其他配置文件(分模块开发)
实际开发中,Spring的配置内容非常多,这就导致Spring配置很复杂且体积很大,所以,可以将部分配置拆解到其他配置文件中,而在Spring主配置文件通过import标签进行加载
<import resource="applicationContext-xxx.xml"/>
4 Spring相关的API
4.1 ApplicationContext的继承体系
applicationContext:接口类型,代表应用上下文,可以通过其实例化获得Spring容器中的Bean对象
4.2 ApplicationContext的实现类
- ClassPathXmlApplicationContext:从根路径下加载配置文件(推荐使用这种)
- FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置
- AnnotationConfigApplicationContext:当使用注解配置容器对象时,需要使用此类来创建Spring容器,用来读取注解
4.3 getBean()方法的使用
- 使用id的唯一标识来获取bean
- 使用bean的字节码来获取bean
5 Spring配置数据源
5.1 数据源(连接池)的作用
- 提高程序性能
- 实现实例化数据源,初始化部分连接资源
- 使用连接资源时从数据源中获取
- 使用完毕后将连接资源归还给数据源
5.2 数据源的手动创建
pom.xml配置文件导入数据源的相关坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>ioc_anno</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
DataSourceTest类的相关代码
public class DataSourceTest {
@Test
// 手动创建c3p0数据源
public void test1() throws PropertyVetoException, SQLException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUser("root");
dataSource.setPassword("root");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
@Test
// 手动创建druid数据源
public void test2() throws PropertyVetoException, SQLException {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
}
读取配置文件形式的创建c3p0数据源
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
@Test
// 手动创建c3p0数据源(加载properties配置文件)
public void test3() throws PropertyVetoException, SQLException {
// 读取配置文件
ResourceBundle rb = ResourceBundle.getBundle("jdbc"); // getBundle里面的参数是基名,不需要扩展名
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(rb.getString("jdbc.driver"));
dataSource.setJdbcUrl(rb.getString("jdbc.url"));
dataSource.setUser(rb.getString("jdbc.username"));
dataSource.setPassword(rb.getString("jdbc.password"));
Connection connection = dataSource.getConnection();
System.out.println(connection);
connection.close();
}
5.3 Spring配置数据源
可以将DataSource的创建权交由Spring创建
pom.xml配置文件导入数据源的相关坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>ioc_anno</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
applicationContext.xml配置信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--加载外部的properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--使用SpEL表达式读取配置文件信息-->
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
</beans>
6 Spring注解开发
6.1 Spring原始注解
Spring是轻代码而重配置的框架,配置比较繁重,影响开发效率,所以注解开发是一种趋势,注解代替xml配置文件可以简化配置,提高开发效率
Spring的原始注解主要是替代Bean的配置
原始注解 | 说明 |
---|---|
@Component | 使用在类上用于实例化Bean |
@Controller | 使用在web层类上用于实例化Bean |
@Service | 使用在service层类上用于实例化Bean |
@Repository | 使用在dao层类上用于实例化Bean |
@Autowired | 使用在字段上用于根据类型依赖注入 |
@Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
@Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
@Value | 注入普通属性 |
@Scope | 标注Bean的作用范围 |
@PostConstruct | 使用在方法上标注该方法是Bean的初始化方法 |
@PreDestroy | 使用在方法上标注该方法是Bean的销毁方法 |
注意:
使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描以便识别使用注解配置的类、字段和方法
<context:component-scan base-package="xxx.xxx"/>
UserDaoImpl类的相关代码:
// <bean id="userDao" class="com.wds.dao.impl.UserDaoImpl"/>
@Repository("userDao") // 这一行相当于上面的bean的创建
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running ....");
}
}
UserServiceImpl类的相关代码:
// <bean id="userService" class="com.wds.service.impl.UserServiceImpl">
@Service("userService")
@Scope("prototype") // 告诉Spring,产生多个Bean对象
@Scope("singleton") // 告诉Spring,产生单个Bean对象
public class UserServiceImpl implements UserService {
@Value("driver打印了") // 注入的是普通属性
private String driver; // 此时的String driver = "driver打印了";
// 我们可以利用Value注入jdbc.properties文件中的值
/*
这里用了SpEL表达式,原因是在applicationContext.xml中我们已经配置了jdbc连接的相关信息
@Value("${jdbc.driver}")
private String driver; // 此时的String driver = "com.mysql.jdbc.Driver";
*/
// <property name="userDao" ref="userDao"/>
// @Autowired // 这里的两个注解相当于上面的property的依赖注入
// @Qualifier("userDao") // 这里的@Qualifier也可以去掉,Spring会按照你要注入的类型从容器中进行匹配的
@Resource(name = "userDao") // 这里的Resource相当于上面两个注解
private UserDao userDao;
@Override
public void save() {
userDao.save();
}
@PostConstruct
public void init(){
System.out.println("Service对象的初始化方法");
}
@PreDestroy
public void destory(){
System.out.println("Service对象的销毁方法");
}
}
applicationContext.xml配置文件的相关配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--加载外部的properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--告诉Spring,组件扫描那些个包下的注解-->
<context:component-scan base-package="com.wds"/>
</beans>
6.2 Spring新注解
根据6.1Spring原始注解的介绍,可知,这些原始注解对于非自定义的Bean是无法使用原始注解开发的,例如,在Spring帮我们创建DataSource时,还有就是Spring的组件扫描等等,由于这些类不是我们自己定义的,所以不能使用原始注解开发,这里就用到新注解
使用上面的原始注解还不能完全的替代xml配置文件,还需要使用新注解替代的配置如下:
- 非自定义的Bean的配置
- 加载properties文件的配置
- 组件扫描的配置
- 引入其他文件
新注解 | 说明 |
---|---|
@Configuration | 用于当前类是一个Spring配置类,当创建容器时会从该类上加载注解 |
@ComponentScan | 用于指定Spring在初始化容器时要扫描的包 |
@Bean | 使用在方法上,标注将该方法的返回值存储到Spring容器中 |
@PropertySource | 用于加载properties配置文件中的配置 |
@Import | 用于导入其他配置类 |
我们先创建SpringConfiguration核心配置类
import org.springframework.context.annotation.*;
// 标志该类是Spring的核心配置类
@Configuration
// <context:component-scan base-package="com.wds"/>
@ComponentScan("com.wds") // 添加组件注解扫描
// <import resource=""/>
@Import({DataSourceConfiguration.class}) // 这里导入DataSourceConfiguration配置类
public class SpringConfiguration {
}
然后我们再创建DataSourceConfiguration配置类
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import javax.sql.DataSource;
// <context:property-placeholder location="classpath:jdbc.properties"/>
@PropertySource("classpath:jdbc.properties") // 将jdbc.properties文件放入到Spring容器当中,使用SpEL表达式获取李敏的值
public class DataSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
/*
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
<property name="user" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
*/
// 写一个方法,方法的返回值就是你想要放入Spring容器当中的Bean的类型
@Bean("dataSource") // Spring会将当前方法的返回值以指定名称存储到容器中
public DataSource getDataSource() throws Exception{
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
System.out.println(dataSource);
return dataSource;
}
}
最后我们写一个main方法做一个测试
public static void main(String[] args) {
// 由于我们采用了全注解开发,所以就不需要配置文件,以Spring核心配置类取代配置文件
AnnotationConfigApplicationContext app = new AnnotationConfigApplicationContext(SpringConfiguration.class);
UserService userService = app.getBean(UserService.class);
userService.save();
}
/*
测试的结果如下:
Service对象的初始化方法
com.mchange.v2.c3p0.ComboPooledDataSource
save running ....
com.mysql.jdbc.Driver
*/
7 Spring集成Junit
7.1 原始Junit测试Spring的问题
在测试类中,每一个测试方法都有以下两行代码:
一个就是加载配置文件,创建Spring容器
一个就是从容器当中获取Bean对象
ClassPathXmlApplicationContext app = new ClassPathXmlApplicationContext("applicationContext.xml");
DataSource dataSource = app.getBean(DataSource.class);
这两行代码,还是必写不可的,不然回报空指针异常
7.2 上述问题解决思路
- 让SpringJunit负责创建Spring容器,但是需要将配置文件的名称告诉它
- 将需要进行测试的Bean直接在测试类中进行注入
7.3 Spring集成Junit步骤
-
导入Spring集成Junit坐标
-
使用@Runwith注解替换原来的运行期
指定是谁帮我去执行这个测试,之前就是之前右键Junit测试,现在去找Spring为我们提供的内核,通过内核去找Junit,内核在找Junit之前,可以帮我们完成很多的工作,例如,Spring容器的创建,配置文件的加载
-
使用@ContextConfiguration指定配置文件或配置类
-
使用@Autowired注入需要测试的对象
-
创建测试方法进行测试
pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>ioc_anno</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
<!--Spring集成Junit坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
</dependencies>
</project>
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private UserService userService;
@Test
public void test1(){
userService.save();
}
}
8 Spring的AOP简介
8.1 AOP的作用及其优势
- 作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
- 优势:减少重复代码,提高开发效率,并且便于维护
8.2 AOP的底层实现
实际上,AOP的底层是通过Spring提供的动态代理技术实现的,在运行期间,Spring通过动态代理技术动态的生成代理对象,代理对象方法执行时进行增强功能的介入,再去调用目标对象的方法,从而完成功能的增强
常用的动态代理技术
- JDK代理:基于接口的动态代理技术
- cglib代理:基于父类的动态代理技术
Spring底层是有两种代理技术,如果你要增强的对象有接口,就使用JDK代理,如果有父类的话,就使用cglib代理
8.2.1 JDK动态代理底层实现的相关代码
目标接口对象
public interface TargetInterface {
void save();
}
目标对象的实现目标接口对象
public class Target implements TargetInterface{
@Override
public void save() {
System.out.println("save running ....");
}
}
增强的对象
public class Advice {
public void before(){
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
}
写一个ProxyTest类,进行测试
public class ProxyTest {
public static void main(String[] args) {
// 目标对象一般使用final修饰
final Target target = new Target();
// 增强对象
final Advice advice = new Advice();
// 返回值,就是动态生成的代理对象,可以使用接口去接受这个代理对象 基于jdk
TargetInterface proxy = (TargetInterface) Proxy.newProxyInstance(
// 目标对象的类加载器
target.getClass().getClassLoader(),
// 与目标对象相同的接口字节码对象数组(因为Java是单继承多实现的,可能不止一个接口)
target.getClass().getInterfaces(),
new InvocationHandler() {
// 调用代理对象的任何方法,实质执行的都是invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强
advice.before();
// 执行目标对象的目标方法
Object invoke = method.invoke(target, args);
// 后置增强
advice.afterReturning();
// 这里的返回就是最终的目标对象的返回值,因为自定义的save方法没有返回值
return invoke;
}
}
);
proxy.save();
}
}
/*
执行的结果如下:
前置增强
save running ....
后置增强
*/
8.2.2 cglib动态代理底层实现的相关代码
在pom.xml中导入cglib的坐标
由于早期的Spring版本没有将cglib集成在Spring当中,所以要在pom.xml中导入cglib坐标,现在不需要,直接使用Spring的上下文就行,Spring已经帮我们将cglib整合到Spring里面
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
目标对象
public class Target{
public void save() {
System.out.println("save running ....");
}
}
增强的对象
public class Advice {
public void before(){
System.out.println("前置增强");
}
public void afterReturning(){
System.out.println("后置增强");
}
}
写一个ProxyTest类,进行测试
public class ProxyTest {
public static void main(String[] args) {
// 目标对象一般使用final修饰
final Target target = new Target();
// 增强对象
final Advice advice = new Advice();
// 返回值,就是动态生成的代理对象,基于cglib
// 1.创建增强器
Enhancer enhancer = new Enhancer();
// 2.设置父类(目标),也就是为增强器设置它爹
enhancer.setSuperclass(target.getClass());
// 3.设置回调,参数需要一个方法解释器,以便进行方法的增强
enhancer.setCallback(new MethodInterceptor() {
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
// 前置增强
advice.before();
// 执行目标对象的目标方法
Object invoke = method.invoke(target, args);
// 后置增强
advice.afterReturning();
// 这里的返回就是最终的目标对象的返回值,因为自定义的save方法没有返回值
return invoke;
}
});
// 4.生成代理对象
// 这里的代理对象是Target的子类,所以可以用父类来接受
Target proxy = (Target) enhancer.create();
proxy.save();
}
}
/*
执行的结果如下:
前置增强
save running ....
后置增强
*/
其实上面的两个动态代理实现的底层代码,在我们用到Spring时,Spring已经帮我们写好了,不需要自己手动的写,如果以后,需要自己编写框架的时候,这些代码需要我们自己手动写,所以上述还是要了解一下,有助于我们知道底层的原理
8.3 AOP相关概念
AOP的相关术语
- Target(目标对象):代理的目标对象
- Proxy(代理):一个类(Target)被AOP织入增强后,就产生一个结果代理类
- JointPoint(连接点):所谓连接点是指那些被拦截到的点(方法)【可以被增强的方法】,在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点
- PointCut(切入点):指要对那些JointPoint进行拦截的定义【通过配置的方式进行增强的连接点】
- Advice(通知/增强):所谓通知就是拦截到的JointPoint之后所要作的事情
- Aspect(切面):切入点和通知的结合
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入,Spring配置的过程就是可以说是织入的过程
8.4 AOP开发明确的事项
8.4.1 需要编写的内容
- 编写核心业务代码(目标类的目标方法)
- 编写切面类,切面类中有通知(增强功能的方法),Spring帮我们把Advice放入到Aspect,所以只需要编写切面类即可
- 在配置文件中,配置织入关系,即将哪些通知与哪些连接点进行结合
8.4.2 AOP技术实现的内容
Spring框架监控切入点方法的执行,一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行
8.4.3 AOP底层使用哪种代理方式
在Spring中,框架会根据目标类是否实现了接口来决定采用哪种动态代理的方式
8.5 基于XML的AOP开发
8.5.1 快速入门
Spring中也有AOP的相关JAR包,但是Spring还是推荐使用AspectJ来进行AOP的开发,于是Spring融入了AspectJ的第三方JAR包
- 导入AOP相关坐标
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给Spring
- 在applicationContext.xml中配置织入关系(最重要也是最难的)
- 测试代码
pom.xml配置文件的相关配置
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
目标类
public class Target implements TargetInterface {
@Override
public void save() {
int i = 1/0;
System.out.println("save running ....");
}
}
切面类
public class MyAspect {
public void before(){
System.out.println("前置增强");
}
// 正在执行的连接点,就是切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强");
Object proceed = pjp.proceed();
System.out.println("环绕后增强");
return proceed;
}
public void afterThrowing(){
System.out.println("异常抛出异常.....");
}
}
applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--目标对象-->
<bean id="target" class="com.wds.aop.Target"/>
<!--切面对象-->
<bean id="myAspect" class="com.wds.aop.MyAspect"/>
<!--配置织入:告诉Spring框架,哪些方法需要进行哪些增强-->
<aop:config>
<!--声明切面-->
<aop:aspect ref="myAspect">
<!--切面:切点+通知-->
<!--抽取切点表达式-->
<aop:pointcut id="myPointCut" expression="execution(* com.wds.aop.*.*(..))"/>
<aop:around method="around" pointcut-ref="myPointCut"/>
<aop:after-throwing method="afterThrowing" pointcut-ref="myPointCut"/>
</aop:aspect>
</aop:config>
</beans>
编写一个AOPTest类进行测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AOPTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
/*
测试的结果:
环绕前增强
save running ....
环绕后增强
环绕前增强
异常抛出异常.....
java.lang.ArithmeticException: / by zero
*/
8.5.2 切点表达式的写法
表达式语法:
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以不写
- 返回值类型、包名、类名、方法名可以使用*代表任意
- 包名与类名之间一个点,代表当前包下的类,两个点,表示当前包及其子包下的类
- 参数列表可以使用两个点,表示任意个数,任意类型的参数列表
例如:
// 这里是com包下面的wds包下的aop包的Target类的method方法,并且这个方法没有返回值和参数列表
execution(public void com.wds.aop.Target.method())
// 这里是com包下面的wds包下的aop包的Target类的任意方法,并且这个方法没有返回值和有任意的参数列表
execution(void com.wds.aop.Target.*(..))
// 这里是com包下面的wds包下的aop包下的任意类的任意方法,并且这个方法任意返回值和任意的参数列表(常用)
execution(* com.wds.aop.*.*(..))
// 这里是com包下面的wds包下的aop包及其子包下的任意类的任意方法,并且这个方法任意返回值和任意的参数列表
execution(* com.wds.aop..*.*(..))
execution(* *..*.*(..))
8.5.3 通知的类型
名称 | 标签 | 说明 |
---|---|---|
前置通知 | <aop:before> | 指定增强的方法在切入点方法之前执行 |
后置通知 | <aop:after-returning> | 指定增强的方法在切入点方法之后执行 |
环绕通知 | <aop:around> | 指定增强的方法在切入点方法之前和之后执行 |
异常抛出通知 | <aop:throwing> | 指定增强的方法在出现异常时执行 |
最终通知 | <aop:after> | 无论增强方式执行是否异常都会执行 |
8.6 基于注解的AOP开发
8.6.1 快速入门
基于注解的AOP开发步骤:
- 创建目标接口和目标类(内部有切点)
- 创建切面类(内部有增强方法)
- 将目标类和切面类的对象创建权交给Spring(通过注解将Bean的创建权交给Spring)
- 在切面类中使用注解配置织入关系
- 在配置文件中开启组件扫描和AOP的自动代理
- 测试代码
目标类
@Component("target")
public class Target implements TargetInterface {
@Override
public void save() {
int i = 1/0;
System.out.println("save running ....");
}
}
切面类
@Component("myAspect")
@Aspect // 标注当前MyAspect是一个切面类
public class MyAspect {
// 增强的注解中的括号都是写的切点表达式
// @Before("execution(* com.wds.anno.*.*(..))")
public void before(){
System.out.println("前置增强");
}
// @AfterReturning("execution(* com.wds.anno.*.*(..))")
public void afterReturning(){
System.out.println("后置增强");
}
@Around("execution(* com.wds.anno.*.*(..))")
// 正在执行的连接点,就是切点
public Object around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕前增强");
Object proceed = pjp.proceed();
System.out.println("环绕后增强");
return proceed;
}
@AfterThrowing("execution(* com.wds.anno.*.*(..))")
public void afterThrowing(){
System.out.println("异常抛出异常.....");
}
}
applicationContext-anno.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--在配置文件中开启组件扫描和AOP的自动代理-->
<context:component-scan base-package="com.wds.anno"/>
<aop:aspectj-autoproxy/>
</beans>
编写一个AnnoTest类进行测试代码
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext-anno.xml")
public class AnnoTest {
@Autowired
private TargetInterface target;
@Test
public void test1(){
target.save();
}
}
9 Spring JdbcTemplate基本使用
9.1 JdbcTemplate开发步骤
- 导入spring-jdbc和spring-tx坐标
- 创建数据库表和实体
- 创建JdbcTemplate对象
- 执行数据库操作
基于全注解开发
pom.xml相关配置
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
编写jdbc.properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
编写DataSourceConfiguration配置类
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setUser(username);
dataSource.setJdbcUrl(url);
dataSource.setPassword(password);
return dataSource;
}
}
编写JdbcTemplateConfiguration配置类
public class JdbcTemplateConfiguration {
@Autowired
private DataSource dataSource;
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate(){
return new JdbcTemplate(dataSource);
}
}
编写Spring核心配置类
@Configuration
@ComponentScan("com.wds")
@Import({DataSourceConfiguration.class, JdbcTemplateConfiguration.class})
public class SpringConfiguration {
}
编写数据库实体类
public class Account {
private String name;
private double money;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"name='" + name + '\'' +
", money=" + money +
'}';
}
}
编写JdbcTemplateTest测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class JdbcTemplateTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
public void test1(){
int row = jdbcTemplate.update("insert into account values (?, ?)", "zhangsan", 5000);
System.out.println(row);
}
}
9.1.1 知识要点
更新操作:
jdbcTemplate.update(sql, params)
查询操作:
jdbcTemplate.query(sql, Mapper, params)一般返回的时List集合,集合中的类型由你的Mapper类型决定
jdbcTemplate.queryForObject(sql, Mapper, params)返回单个的对象,对象的类型由你的Mapper类型决定
9.2 事务控制
9.2.1 编程式事务控制
9.2.1.1 编程式事务控制相关对象
PlatformTransactionManager
PlatformTransactionManager接口是Spring提供的事务管理器,它里面提供了我们常用的操作事务的方法
方法 | 说明 |
---|---|
TransactionStatus getTransaction(TransactionDefination defination) | 获取事务的状态信息 |
void commit(TransactionStatus status) | 提交事务 |
void rollback(TransactionStatus status) | 回滚事务 |
注意:
PlatformTransactionManger是接口类型,里面定义了事务控制的行为,根据不同的Dao层技术则有不同的实现类,例如:Dao层技术是jdbc或mybatis时,org.springframework.jdbc.datasource.DatasourceTransactionManager,如果是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager
TransactionDefination
TransactionDefination是事务的定义信息对象,里面的方法如下:
方法 | 说明 |
---|---|
int getIsolationLevel() | 获得事务的隔离级别 |
int getPropogationBehavior() | 获得事务的传播行为 |
int getTimeout() | 获得超时时间 |
boolean isReadOnly() | 是否只读 |
设置隔离级别,可以解决事务并发产生的问题,如:
1、脏读
所谓脏读,就是指事务A读到了事务B还没有提交的数据,比如银行取钱,事务A开启事务,此时切换到事务B,事务B开启事务–>取走100元,此时切换回事务A,事务A读取的肯定是数据库里面的原始数据,因为事务B取走了100块钱,并没有提交,数据库里面的账务余额肯定还是原始余额,这就是脏读。
2、不可重复读
所谓不可重复读,就是指在一个事务里面读取了两次某个数据,读出来的数据不一致。还是以银行取钱为例,事务A开启事务–>查出银行卡余额为1000元,此时切换到事务B事务B开启事务–>事务B取走100元–>提交,数据库里面余额变为900元,此时切换回事务A,事务A再查一次查出账户余额为900元,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
3、幻读
所谓幻读,就是指在一个事务里面的操作中发现了未被操作的数据。比如学生信息,事务A开启事务–>修改所有学生当天签到状况为false,此时切换到事务B,事务B开启事务–>事务B插入了一条学生数据,此时切换回事务A,事务A提交的时候发现了一条自己没有修改过的数据,这就是幻读,就好像发生了幻觉一样。幻读出现的前提是并发的事务中有事务发生了插入、删除操作。
隔离级别如下:
1、DEFAULT
默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。顺便说一句,如果使用的MySQL,可以使用"select @@tx_isolation"来查看默认的事务隔离级别
2、READ_UNCOMMITTED
读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用
3、READ_COMMITED
读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读
4、REPEATABLE_READ
重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决
5、SERLALIZABLE
串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
事务传播行为
- required:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中,一般的选择(默认值)
- supports:支持当前事务,如果没有当前事务,就以非事务的方式执行(没有事务)
- mandatory:使用当前的事务,如果当前没有事务,就抛出异常
- requers_new:新建事务,如果当前在事务中,就把事务挂起
- not_supported:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
- never:以非事务方式执行操作,如果当前存在事务,抛出异常
- nested:如果当前存在事务,则在嵌套事务内执行。如果没有当前事务,则执行required类似的操作
- 超时时间:默认值是-1,没有超时限制,如果有,以秒为单位进行设置
- 是否只读:建议查询时设置为只读
事务状态对象
TransactionStatus接口提供的是事务具体的运行状态。
方法 | 说明 |
---|---|
boolean hasSavepoint() | 是否存储回滚点 |
boolean isCompleted() | 事务是否完成 |
boolean isNewTransaction() | 是否是新事务 |
boolean isRollbackOnly() | 事务是否回滚 |
9.2.2 知识要点
编程式事务控制三大对象
PlatformTransactionManager
TransactionDefination
TransactionStatus
PlatformTransactionManager + TransactionDefination = TransactionStatus
在进行声明式事务控制时,第一个和第二个对象需要在Spring配置文件中,进行配置,告诉Spring,Dao层是用什么平台的事务管理器,告诉Spring,控制事务的一些参数有哪些
9.3 基于XML的声明式事务控制
Spring的声明式事务控制顾名思义就是采用声明的方式来处理事务,这里所说的声明,就是指在配置文件中声明,用在Spring配置文件中声明式的处理事务来代替代码式的处理事务
声明式事务控制处理的作用(Spring声明式事务控制底层就是AOP)
- 事务管理不侵入开发的组件(解耦合),具体来说,业务逻辑对象就不会意识到正在事务管理之中,事实上也应该如此,因为事务管理是属于系统层面的服务,而不是业务逻辑的一部分,如果想要改变事务管理策划的话,也只需要在定义文件中重新配置即可
- 在不需要事务管理的时候,只要在设定文件上修改一下,即可移去事务管理服务,无需改变代码重新编译,这样维护起来及其方便
导入相关的坐标
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.32</version>
</dependency>
<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>compile</scope>
</dependency>
applicationContext.xml相关配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/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">
<!--配置平台事务管理器-->
<!--平台事务管理器会从datasource里面获取一个connection对象来进行事务控制-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--通知 事务增强-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--设置事务的属性信息-->
<tx:attributes>
<tx:method name="transfer" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="save" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="false"/>
<tx:method name="findAll" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="update" isolation="REPEATABLE_READ" propagation="REQUIRED" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务的aop织入-->
<aop:config>
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.wds.service.impl.*.*(..))"/>
</aop:config>
<aop:aspectj-autoproxy/>
<!--事务的注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
其中,<tx:method>代表切点方法的事务参数的配置
- name:切点方法的名称
- isolation:事务的隔离级别
- propogation:事务的传播行为
- timeout:超时时间
- read-only:是否只读
9.4 基于注解的声明式事务控制
DataSourceConfiguration类
@PropertySource("classpath:jdbc.properties")
public class DataSourceConfiguration {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean("dataSource")
public DataSource getDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setUser(username);
dataSource.setJdbcUrl(url);
dataSource.setPassword(password);
return dataSource;
}
}
JdbcTemplateConfiguration类
public class JdbcTemplateConfiguration {
@Autowired
private DataSource dataSource;
@Bean("jdbcTemplate")
public JdbcTemplate getJdbcTemplate(){
return new JdbcTemplate(dataSource);
}
}
TransactionConfiguration类
public class TransactionConfiguration {
@Autowired
private DataSource dataSource;
@Bean("transactionManager")
public PlatformTransactionManager getTransactionManager(){
return new DataSourceTransactionManager(dataSource);
}
}
SpringConfiguration类
@Configuration
@ComponentScan("com.wds")
@EnableAspectJAutoProxy
@EnableTransactionManagement
@Import({DataSourceConfiguration.class, JdbcTemplateConfiguration.class, TransactionConfiguration.class})
public class SpringConfiguration {
}
AccountDaoImpl类
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void out(String outMan, double money) {
jdbcTemplate.update("update account set money = money - ? where name = ?", money, outMan);
}
@Override
public void in(String inMan, double money) {
jdbcTemplate.update("update account set money = money + ? where name = ?", money, inMan);
}
}
AccountServiceImpl类
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Resource(name = "accountDao")
private AccountDao accountDao;
@Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRED, readOnly = false, timeout = -1)
@Override
public void transfer(String outMan, String inMan, double money) {
accountDao.out(outMan, money);
int i = 1/0;
accountDao.in(inMan, money);
}
}
AccountController测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountController {
@Resource(name = "accountService")
private AccountService accountService;
@Test
public void test1(){
accountService.transfer("tom", "zhangsan", 500);
}
}