了解Spring框架

什么是Spring

Spring是分层的JavaSE/EE full-stack 轻量级开源框架,以IoC(Inverse of Control 控制反转)和AOP(Aspect Oriented Programming 面向切面编程)为内核,使用基本的JavaBean来完成以前只可能由EJB完成的工作,取代了EJB的臃肿、低效的开发模式。

在实际开发中,通常服务器端在采用三层体系架构,分别为表示层(Web)、业务逻辑层(Service)、持久层(Dao), Spring对每一层都提供了技术支持。

  • 表示层:在表示层提供了与Struts等框架的整合
  • 业务逻辑层:在业务逻辑层可以管理事务、记录日志等
  • 持久层(Dao即DataAccessobjects 数据存取对象):在持久层可以整合Hibernate、JdbcTemplate等技术

Spring具有简单、可测试和松耦合等特点。Spring不仅可以用于服务器端开发,也可以应用于任何Java应用的开发中,以下是它的7大优点:

  1. 非侵入式设计
  2. 方便解耦、简化开发
  3. 支持AOP(Aspect Oriented Programming java的面向切面编程)
  4. 支持声明式事务处理
  5. 方便程序测试
  6. 方便集成各种优秀框架、
  7. 降低Java EE API的使用难度

Spring的体系结构

以下提及的模块只是重要的模块,其他不重要的若有兴趣请自行百度

Spring框架体系结构

  • 核心容器(Core Container)

    • Bean模块:提供了BeanFactory,Spring的管理对象称为Bean
    • Core模块:提供看Spring框架的基本组成部分,包括Ioc(控制反转)Di(依赖注入)
    • Context模块:建立在Core和Beans模块的基础之上,它是访问定义和配置的任何对象的媒介
  • Data Access/Integration(数据访问集成)

    • JDBC:提供了一个JDBC的抽象层,大幅度的减少了在开发过程中对数据库操作的编码
    • ORM:对流行的对象关系映射API,包括JPA、JDO和Hibernate提供了集成层支持
    • Transactions支持对实现特殊接口以及所有POJO类的编程和声明式的事务管理
  • Web

    • Servlet:也称Spring-webmvc模块,包含Spring模型—视图—控制器(MVC)和REST Web Services实现的Web程序
    • Web:提供了基本的Web开发集成特性,如:多文件上传、使用Servlet监听器来初始化IoC容器以及Web应用上下文
  • 其他模块

    • Aop:提供了面向切面编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以降低耦合性
    • Aspects: 提供了与AspectJ的集成功能,AspectJ是一个功能强大且成熟的面向切面编程(AOP)框架

导入Spring框架包

需要下载Spring框架包地址:https://repo.spring.io/simple/libs-release-local/org/springframework/spring/选择合适的版本,以下使用的Spring4.3.10
第三方依赖包地址:https://mirror-hk.koddos.net/apache//commons/logging/binaries/commons-logging-1.2-bin.zip
还需要注意的是IDEA导入库文件到项目中(位于同一Project目录中)后直接作用于于整个Project,其中的所有Modules可以共用导入进来的所有库,共用方法下面有介绍

Bean的装配方式

基于XML方式装配

基于Annotation(译:注解)的装配方式

基于XML的装配可能会导致XML配置文件过于臃肿,给后续的维护和升级带来一定的困难。为此,Spring提供了对Annotation(注解)技术的全面支持
首先要导入包aop-4.3.10.RELEASE.jar(本文章之前配置环境内容下载的包),不然会报错

主要注释:

  • @Component:用于描述Spring中的Bean,它是一个泛化的概念。
  • @Repository:用于将数据访问层(DAO DataAccessobjects 数据存取对象)的类标识为Spring中的Bean
  • @Service:用于将业务层(Service)的类标识为Spring中的Bean
  • @Controller:用于将控制层(Controller)的类标识为Spring中的Bean 。
  • @Autowired:用于对Bean的属性变量、属性的setter方法及构造方法进行标注,配合对应的注解处理器完成Bean的自动配置工作。
  • @Resource:其作用与Autowired一样。@Resource中有两个重要属性:name和type。Spring将name属性解析为Bean实例名称,type属性解析为Bean实例类型。
  • @Qualifier:与@Autowired注解配合使用,会将默认的按Bean类型装配修改为按Bean的实例名称装配,Bean的实例名称由@Qualifier注解的参数指定。

目录结构

在Module新建两个接口com/it/annotation/UserDao.javacom/it/annotation/UserService.java

1
2
3
4
5
6
package com.it.aspectj.PointCutClass;

public interface UserDao {
public void save();
}

1
2
3
4
5
package com.it.annotation;

public interface UserService {
public void save();
}

分别创建以上的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
package com.it.annotation;
import org.springframework.stereotype.Repository;

@Repository("userDao")
public class UserDaoImpl implements UserDao{
@Override
public void save() {
System.out.println("UseDao Save!!");
}

}

以上的@Repository相当于在JavaBean的xml配置文件中写入了一个Spring中的Bean,例如:<bean id="userDao" class="com.it.annotation.UserDaoImpl"/>,只不过这个Bean标注属于数据访问层的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.it.annotation;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service("userService")
public class UserServiceImpl implements UserService{
@Resource(name="userDao")
private UserDao userDao;
@Override
public void save() {
this.userDao.save();
System.out.println("UserService Save!!");
}
}

以上的@Resource标签的作用就是读取Bean配置文件.xml,获取Beans后找到有一个名为UserDao的Bean,然后将这个Bean传入UserServiceImpl.class中存入私有变量userDao中去,相当于xml配置文件中的:

1
2
3
4
<bean id="UserDao" class="com.itheima.ioc.UserDaoImp"/>
<bean id="DiTestPrint" class="com.itheima.ioc.DIserviceImp">
<property name="UserDao" ref="UserDao"/> <!--写在bean里可理解为DiTestPrint方法必须有依赖UserDaoImp方法才可以-->
</bean>

再建立一个com/it/annotation/UserController.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.it.annotation;
import org.springframework.stereotype.Controller;
import javax.annotation.Resource;

@Controller("userController")
public class UserController {
@Resource(name="userService")
private UserService userService;
public void save(){
this.userService.save();
System.out.println("Controller Save!!");

}

}

创建一个自动扫描那些标注Bean的xml文件com/it/annotation/Beans.xml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?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">
<!-- bean definitions here -->
<!-- 此方式未成功,书上说是可以,但我觉得不开启扫描,java源文件里面的标注是没有作用的,还需要添加<property>才可以引入需要的依赖 -->
<!--<bean id="userDao" class="com.it.annotation.UserDaoImpl"/>
<bean id="userService" class="com.it.annotation.UserServiceImpl"/>
<bean id="userController" class="com.it.annotation.UserController"/>-->
<!--以下测试成功!!-->
<context:component-scan base-package="com.it.annotation"></context:component-scan>

</beans>

再创建main类,获取xml文件内容以及实例化xml中的Bean:

其中的Beans.xml文件获取在IDEA中只能以下面的路径进行,不能像书上那样的MyEclips中的方式:com/it/annotation/Beans.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.it.annotation;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AnnotationAssembleTest {
public static void main(String[] a){
String xmlP= "src/main/java/com/it/annotation/Beans.xml";
ApplicationContext applicationContext=new ClassPathXmlApplicationContext(xmlP);
UserController userController=(UserController) applicationContext.getBean("userController");
userController.save();
}
}

打印输出以下内容,证明成功:

UseDao Save!!
UserService Save!!
Controller Save!!

Process finished with exit code 0

Spring的数据库开发

本章最主要的是JdbcTemplate,它是Spring JDBC的核心类,Spring框架的数据库开发基本都是围绕这个类来进行的,继承关系如下:

JdbcTemplate继承关系

直接父类JdbcAccessor提供以下:

  • DataSource:获取数据库连接,还可以引入对数据库连接的缓冲池和分布式事务的支持。
  • JdbcOperations:接口定义了在JdbcTemplate类中可以使用的操作集合,包括添加、修改、查询和删除等操作。
  • SQLExceptionTranslator:该接口负责对SQLException进行转译工作。通过必要的设置获取SQLExceptionTranslator中的方法,可以使JdbcTemplate在需要处理SQLException时,委托SQLExceptionTranslator的实现类来完成相关的转译工作。

Spring JDBC模块主要由4个包组成:

SpringJDBC包分布情况

Spring数据库开发所需的jar包

请参考本文章之前的IDAR导入库以及添加classpath方法

SpringJDBC包

  • 第一个是Java数据库连接,(Java Database Connectivity,简称JDBC)是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法,jar包下载https://downloads.mysql.com/archives/c-j/
  • SpringJDBC的jar包(包含于Spring框架包)
  • Spring事物处理的jar包(包含于Spring框架包)

excute()方法执行sql语句

首先创建xml配置SpringBean的文件(书上基本上文件名称都是applicationContext.xml,但是Bean容器文件可以放于任何项目下的目录当中,且任意名称即可,只要在相应的ClassPathXmlApplicationContext()函数中写对xml路径即可),此文件名applicationJDBC.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/ding"/> <!--此处如果有远程数据库应该替换localhost为远程主机的地址或者域名,后面的ding是指在主机下的Mysql下的database名称-->
<property name="username" value="数据库用户名"/>
<property name="password" value="用户名的密码"/>
</bean>
<bean id="jdbcT" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>

以上的两个Bean的class分别来自上面导入的包,其中的<property name="dataSource" ref="dataSource"></property>说明JdbcTemplate类中一定包含setDataSource的类似于构造方法的函数将DriverManagerDataSource类传递过去作为依赖。

创建含有excute()方法的Java文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.example.JDBC_Template;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;

public class jdbcTemplateTest {

public static void main(String[] args) {
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
JdbcTemplate jdbcTemplate=(JdbcTemplate)applicationContext.getBean("jdbcT"); //获取JdbcTemplate对象
jdbcTemplate.execute("create table student(" + //使用JdbcTemplate中的执行sql语言的方法
"id int primary key auto_increment," +
"name varchar(50)," +
"grade varchar(100)," +
"age int )");
System.out.println("~~~~~~~~~~~~~~~创建表格成功~~~~~~~~~~~~~~~~~~");

}
}

如果运行成功,则会打印~~~~~~~~~~~~~~~创建表格成功~~~~~~~~~~~~~~~~~~,并在相应的数据库表中可查看表结构:

1
2
3
4
5
6
7
8
9
10
MySQL [fuding]> desc student;
+-------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(50) | YES | | NULL | |
| grade | varchar(100) | YES | | NULL | |
| age | int | YES | | NULL | |
+-------+--------------+------+-----+---------+----------------+
4 rows in set (0.018 sec)

注意: 建表时一定要优先使用英文字段名,并且于JavaBean中的变量名称保持一致,不然会出现执行jdbcTemplate.query(all,rows)方法不成功(具体原因往下参阅),就会出现:

SudentInfo{id=0, name='null', age=0, grade='null'}
SudentInfo{id=0, name='null', age=0, grade='null'}
SudentInfo{id=0, name='null', age=0, grade='null'}

update()方法,插入、更新、删除操作

update()方法可以完成插入、更新和删除数据的操作。在JdbcTemplate类中,提供了一系列的update()方法,其常用方法下表所示:

update

虽然一共有4中使用方式,但最常用的还是第四种方式,下面的插入、更新、删除都是以第4种的方式来写的:

创建JavaBean,定义好于数据库字段一致且类型一致的变量,右击鼠标->Generate->Getter and Setter生成以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.example.JDBC_Template.Update_Query;

public class SudentInfo {
int id; //要于数据库表中的字段名一致
String name; //要于数据库表中的字段名一致
int age; //要于数据库表中的字段名一致
String grade; //要于数据库表中的字段名一致

public int getId() {
return id;
}

public void setId(int id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public int getAge() {
return age;
}

public void setAge(int age) {
this.age = age;
}


public String getGrade() {
return grade;
}

public void setGrade(String grade) {
this.grade = grade;
}
@Override
public String toString() {
return "SudentInfo{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
", grade='" + grade + '\'' +
'}';
}

}

创建Dao设计模式的接口StudentDao:

1
2
3
4
5
6
7
8
9
10
package com.example.JDBC_Template.Update_Query;

import java.util.List;

public interface StudentDao {
public int addStudent(SudentInfo sudentInfo); //用于添加student数据,给除了id的信息,因为id在数据表中为自增约束
public int updateStudent(SudentInfo sudentInfo); //用于更新student数据,给全部信息
public int delStudent(int id); //用于删除student数据,给删除的id即可
}

创建实现以上的接口的方法StudentDaoImp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package com.example.JDBC_Template.Update_Query;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import java.util.List;

public class StudentDaoImp implements StudentDao{
JdbcTemplate jdbcTemplate;

//下面的setJdbcTemplat方法,是为了在配置文件中的<property name="jdbcTemplate" ref="jdbcT"></property>传入依赖,前面有提到
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}


@Override
public int addStudent(SudentInfo sudentInfo) {
String sql="insert into student(name,grade,age) values(?,?,?)";
Object[] objects=new Object[]{ //Object数组,可存放任何java中对象或者数据
sudentInfo.getName(),
sudentInfo.getGrade(),
sudentInfo.getAge(),
};

int rs=jdbcTemplate.update(sql,objects);
return rs;
}

@Override
public int updateStudent(SudentInfo sudentInfo) {
String sql="update student set name=?,grade=?,age=? where id=?";
Object[] objects=new Object[]{
sudentInfo.getName(),
sudentInfo.getGrade(),
sudentInfo.getAge(),
sudentInfo.getId()

};
int rs=jdbcTemplate.update(sql,objects);

return rs;
}

@Override
public int delStudent(int id) {
String sql="delete from student where id=?";

return jdbcTemplate.update(sql,id);
}
}

由于StudentDaoImp方法需要依赖JdbcTemplate,于是在相应的配置文件中装配为Bean,配置依赖的方法:

1
2
3
<bean id="studentDao" class="com.example.JDBC_Template.Update_Query.StudentDaoImp">
<property name="jdbcTemplate" ref="jdbcT"></property> <!--此处的ref属性要与上面的JdbcTemplate的Bean的id一致,但不需要于它的name一致-->
</bean>

注意: property标签中 name="jdbcTemplate"一定要StudentDaoImpclass中有与其对应的setJdbcTemplate(类似于构造方法)

创建执行以上update的方法:

在软件开发过程中,需要有相应的测试工作。依据测试目的不同,可以将软件测试分为单元测试、集成测试、确认测试和系统测试等。其中单元测试在软件开发阶段是最底层的测试,它易于及时发现并解决问题。JUnit就是一个进行单元测试的开源框架,为了方便测试,需要将Junit库导入classpath,来使用Junit。

以下代码中的@Test即是Junit用来单元测试的注释,如果要在IDEA中正常使用,操作如下:

在要进行单元测试的方法前加入@Test->出现红色提示->将鼠标指向红色Test->点击Add Junit to ClassPath此操作完成后要测试的方法右边就会出现一个绿色的小三角形(点击可运行没有在mian方法中的代码)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.example.JDBC_Template.Update_Query;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class UpdateQuery {
public static void main(String[] args) { //添加数据
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
StudentDao studao=(StudentDao) applicationContext.getBean("studentDao");
SudentInfo sudentInfo=new SudentInfo();
sudentInfo.setAge(22);
sudentInfo.setGrade("1班");
sudentInfo.setName("付");
int sturs =studao.addStudent(sudentInfo);
System.out.println("添加了"+sturs+ "行学生数据!!");
}

@Test
public void DeleteStu(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
StudentDao studao=(StudentDao) applicationContext.getBean("studentDao");
System.out.println("删除了"+studao.delStudent(2)+"行学生数据");
}
@Test
public void upDate(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
StudentDao studao=(StudentDao) applicationContext.getBean("studentDao");
SudentInfo stu=new SudentInfo();
stu.setName("王麻子");
stu.setAge(100);
stu.setGrade("终极一班");
stu.setId(1);
System.out.println("更改了"+studao.updateStudent(stu)+"行!!");
}
}

query()处理各种对数据库表的查询操作

JdbcTemplate类中还提供了大量的query()方法来处理各种对数据库表的查询操作。其中,常用的几个query()方法如下表所示:

query方法

以上的1和4都是常用的

接口StudentDao中添加以下两个接口:

1
2
public List<SudentInfo> QueryAllInfo();     //查询所有学生
public SudentInfo QueryA(int id); //按id查询一位学生

StudentDaoImp中添加实现以上的接口的方法:

sping中的RowMapper可以将数据中的每一行数据封装成用户定义的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public List<SudentInfo> QueryAllInfo() {
String all="select * from student";
RowMapper<SudentInfo> rows=new BeanPropertyRowMapper<SudentInfo>(SudentInfo.class);
return jdbcTemplate.query(all,rows);
}

@Override
public SudentInfo QueryA(int id) {
String stu="select * from student where id=?";
RowMapper<SudentInfo> rowMapper=new BeanPropertyRowMapper<>(SudentInfo.class);
return jdbcTemplate.queryForObject(stu,rowMapper,id);
}

运行测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.example.JDBC_Template.Update_Query;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class UpdateQuery { //打印多行数据
@Test
public void QueryAllStudents(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
StudentDao studao=(StudentDao) applicationContext.getBean("studentDao");
List<SudentInfo> list= studao.QueryAllInfo();
for (SudentInfo i:list) {
System.out.println(i);
}
}
@Test
public void QueryAStudent(){ //打印id=2的一行数据
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
StudentDao studao=(StudentDao) applicationContext.getBean("studentDao");
SudentInfo s=studao.QueryA(2);
System.out.println(s);
}
}

运行结果:

QueryAllStudents():

SudentInfo{id=1, name='付鼎', age=9, grade='三年级'}
SudentInfo{id=2, name='付鼎鼎', age=22, grade='专升本1班'}

QueryAStudent():

SudentInfo{id=2, name='付鼎鼎', age=22, grade='专升本1班'}

Spring的事物管理

在实际开发中,操作数据库时都会涉及到事务管理问题,为此Spring提供了专门用于事务处理的API。Spring的事务管理简化了传统的事务管理流程,并且在一定程度上减少了开发者的工作量。

需要之前Spring的数据库开发图中的所有包,详情请看Spring的数据库开发所需的jar包

在Spring的所有JAR包中,包含一个名为spring-tx-4.3.6.RELEASE的JAR包,该包就是Spring提供的用于事务管理的依赖包,包中有3个重要的接口:

  • PlatformTransactionManager,用于获取事务状态信息
    • TransactionStatus getTransaction(TransactionDefinition definition);用于获取事务状态信息
    • void commit(TransactionStatus status);用于提交事务
    • void rollback(TransactionStatus status);用于回滚事务
  • TransactionDefinition,用于提交事务
    • String getName( );获取事务对象名称
    • int getIsolationLevel( );获取事务的隔离级别
    • int getPropagationBehavior( );获取事务的传播行为
    • int getTimeout( );获取事务的超时时间
    • boolean isReadOnly( );获取事务是否只读
  • ransactionStatus,用于回滚事务
    • void flush();刷新事务
    • boolean hasSavepoint();获取是否存在保存点
    • boolean isCompleted();获取事务是否完成
    • boolean isNewTransaction();获取是否为新事务
    • boolean isRollbackOnly();获取事务是否回滚
    • void setRollbackOnly();设置事务回滚

上述方法中,事务的传播行为是指在同一个方法中,不同操作前后所使用的事务。传播行为有很多种,具体如下表所示:

在事务管理过程中,传播行为可以控制是否需要创建事务以及如何创建事务,通常情况下,数据的查询不会影响原数据的改变,所以不需要进行事务管理,而对于数据的插入、更新和删除操作,必须进行事务管理。如果没有指定事务的传播行为,Spring默认传播行为是REQUIRED。

传播行为

基于XML方式

事务管理方式有两种:编程式事务管理、声明式事务管理
声明式事务管理:通过AOP技术实现的事务管理,主要思想是将事务作为一个“切面”代码单独编写,然后通过AOP技术将事务管理的“切面”植入到业务目标类中

基于XML方式配置图

配置<tx:advice>元素的重点是配置<tx:method>子元素,上图中使用灰色标注的几个属性是<tx:method>元素中的常用属性。其属性描述具体如下:

<tx:advice>子元素属性说明

在之前的StudentDao接口中添加以下代码:

1
public void transfer(String outUser,String inUser,Double money);        //传入outUser是支付用户、inUser是收款用户、money是支付多少金额

在之前的StudentDaoImp实现类中添加实现方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Override
public void transfer(String outUser, String inUser, Double money) {
jdbcTemplate.update("update client set balance=balance-?"
+"where name=?",money,outUser
);

int i=20/0; //模拟事务在运行过程中出错,比如服务器宕机

jdbcTemplate.update(
"update client set balance =balance+?" +
"where name=?",money,inUser
);
}

在之前的applicationJDBC.xml添加以下代码:

在ntelliJ IDEA 2020.3.2 (Ultimate Edition)版本上测试,写xml配置文件时自动生成的spring XML配置文件引用的xml约束不对,作者去官网找到了比较全面的bean引用的约束,写于下方代码中才可以使配置元素 ‘tx:advice’ 中允许出现属性 ‘transaction-manager’

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.3.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.3.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://主机名:3306/数据库名"/>
<property name="username" value="用户名"/>
<property name="password" value="密码"/>
</bean>
<bean id="jdbcT" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<bean id="studentDao" class="com.example.JDBC_Template.Update_Query.StudentDaoImp">
<property name="jdbcTemplate" ref="jdbcT"></property>
</bean>
<!--JDBC事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--这个class属性配置的类
就是继承了三个接口的方法于是它实现所有接口的功能-->
<property name="dataSource" ref="dataSource"></property> <!--将用户名密码数据库信息依赖注入到事物管理器才可以操作事物-->
</bean>

<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/>
<!--name="*"表示任意方法都开启-->
<!--配置事物属性的方法来自继承TransectionDefinition实现的方法,所以需要transaction-manager="transactionManager"-->
</tx:attributes>
</tx:advice>

<aop:config> <!--理解为这个是主动的,那些方法是被动被AOP加入增强方法(事务管理)的,于是只要某个方法里出现异常就会触发事物管理的回滚功能,从而不会使没有提交更改的事物(或者说方法)执行成功更改数据库-->
<!--配置切入点-->
<aop:pointcut id="txPointcut" expression="execution(* com.example.JDBC_Template.Update_Query.*.*(..))"/> <!--第一个*代表所有返回值类型、第二个*代表该包下的所有类、第三个*代表该类的所有方法、(..)代表方法中的任意参数-->
<!--将切面txAdvice、切入点txPointcut"放入通知器aop:advisor整合-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"></aop:advisor>

</aop:config>
</beans>

编写测试方法:

1
2
3
4
5
6
7
8
9
10
public class TransactionTest {
@Test
public void xmlTest(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
StudentDao studentDao= (StudentDao)applicationContext.getBean("studentDao");
studentDao.transfer("fuding","malei",1000.1);
System.out.println("转账成功!!");
}
}

如果事物管理器生效,执行以上代码会出现:

模拟突发情况生效
执行不成功,数据库表未被更改

注释掉int i=20/0;,就会执行成功:

无突发情况
执行成功,数据库表被更改

基于注解方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!--注释部分说明要去除以下配置代码-->
<!--<tx:advice id="txAdvice" transaction-manager="transactionManager">

<tx:attributes>
<tx:method name="*" propagation="REQUIRED" isolation="DEFAULT" read-only="false"/> 配置事物属性的方法来自继承TransectionDefinition实现的方法
</tx:attributes>
</tx:advice>

<aop:config>
配置切入点
<aop:pointcut id="txPointcut" expression="execution(* com.example.JDBC_Template.Update_Query.*.*(..))"/> 第一个*代表所有返回值类型、第二个*代表该包下的所有类、第三个*代表该类的所有方法、(..)代表方法中的任意参数
将切面txAdvice、切入点txPointcut"放入通知器aop:advisor整合
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"></aop:advisor>
</aop:config>-->

<!--JDBC事务管理-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!--这个class属性配置的类
就是继承了三个接口的方法于是它实现所有接口的功能-->
<property name="dataSource" ref="dataSource"></property> <!--将用户名密码数据库信息依赖注入到事物管理器才可以操作事物-->
</bean>

<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven> <!--注册事物注解驱动-->
<!--注解驱动扫描可以理解为:比如扫描Bean可能是Spring核心类就有提供驱动,对应:<context:component-scan base-package="com.it.aspectj"/>,AOP的AspectJ方式就是导入支持jar包提供驱动,对应:<aop:aspectj-autoproxy/>-->

再将TransactionTest.java中添加:

1
2
3
4
5
6
7
8
9
10
public class TransactionTest {
@Test
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false) //添加此处代码
public void xmlTest(){
ApplicationContext applicationContext=new ClassPathXmlApplicationContext("applicationJDBC.xml");
StudentDao studentDao= (StudentDao)applicationContext.getBean("studentDao");
studentDao.transfer("fuding","malei",1000.1);
System.out.println("转账成功!!");
}
}

测试运行:

运行成功
查询数据表结果

突发情况,运行失败
数据表未被更改