目录
4、在resources文件夹中创建一个任意名称的xml文件
5、让Spring管理资源,在配置文件中配置service和dao
[(1) IOC容器解析](#(1) IOC容器解析)
2、IOC原理实现---环境搭建:构建maven工程,引入依赖
2)配置Spring容器管理NewItemFactory类型对象
(2)配置Account类(利用setter方法注入依赖数据)
(2)配置Account类(利用setter注入依赖集合数据)
(四)编写持久层代码AccountDao以及实现类AccountDaoImpl
(五)编写业务层代码Accountservice以及实现类AccountServiceImpl
[3、使用@ContextConfiguration 指定 Spring 配置文件的位置](#3、使用@ContextConfiguration 指定 Spring 配置文件的位置)
[4、使用@Autowired 给测试类中的变量注入数据](#4、使用@Autowired 给测试类中的变量注入数据)
(二)编写ConnectionUtils工具类管理数据库连接
[(2)使用aop:config 声明AOP配置](#(2)使用aop:config 声明AOP配置)
[(3)使用aop:aspect 配置切面](#(3)使用aop:aspect 配置切面)
(3)沿用转账业务的代码:dao实现类和service实现类采用注解的形式,添加到容器中管理
一、Spring概述
(一)、Spring是什么?
Spring是一个分层的java SE/EE full-stack(一站式)轻量级开源框架,以IOC(控制反转)和AOP(面向切面编程)为内核。
在java三层架构当中分别提供了相应的技术:
表现层(web层):SpringMVC框架
业务层(service层):Bean管理(IOC容器)
持久层(dao层):jdbcTemplate模板对象以及提供了ORM模块整合其他优秀的持久层技术。
(二)、Spring框架发展历程
1997年,IBM提出了EJB的思想。1998年,SUN制定开发标准规范EJB1.0.。199年EJB1.1发布。2001年,EJB2.0发布。2006年EJB3.0发布。
Rod Johnson ( Spring 之父) Expert One-to-One J2EE Design and Development(2002) 阐述了 J2EE
使用EJB 开发设计的优点及解决方案 Expert One-to-One J2EE Development without EJB(2004) 阐述了 J2EE 开发不使用 EJB的解决方式(Spring 雏形)
2017 年 9 月份发布了 Spring 的最新版本 Spring5.0 通用版(GA)
(三)、Spring框架的优势
方便解耦,简化开发:Spring就是一个工厂,可以管理所有对象的创建和依赖关系维护,交给Spring管理。
AOP编程的支持:可以方便的实现对程序进行权限拦截,日志记录,运行的监控。
声明式事务的支持:通过配置的方式完成对事务的管理,无需手动编程。
方便程序的测试:对Junit支持,可以通过注解方式方便的对Spring程序进行测试。
整合外部优秀技术:Spring内部提供了对各种优秀框架(Hibernate,Mybatis)的直接支持。
javaEE技术的封装;Spring对javaEE开发当中复杂难用的API进行封装,降低了这些API的使用难度。
(四)、Spring的体系结构
二、程序耦合与解耦合
(一)什么是程序的耦合
程序的耦合是程序之间的关联性,也就是多个类的联系是否紧密,多个对象之间的关系是否密切。
生活中的案例:
你的房子里面有窗子,那么房子和窗子就有了关联
耦合度是松还是紧就看你的关联是强还是弱,也就是修改的代价,比如你窗子是扣死在墙里的那么你修改窗子就必须修改墙 这就比较紧密了,反应在程序上就是耦合度高,不利于程序的扩展和维护。
但是如果你窗子是按照某种规格的 可以自由拆装的,那么修改的代价就小,耦合度也就低了,反应在程 序上就是耦合度低,利于程序的扩展和维护。
我们写程序的目标就是 高内聚 低耦合!
(二)解决程序耦合的思路
1、编译不依赖,运行时才依赖
当我们讲解jdbc时,是通过反射来注册驱动的,代码如下:
java
Class.forName("com.mysql.jdbc.Driver");//使用的驱动类是指定了一个字符串
我们的类中在编译阶段不再需要具体的驱动类,就算删除mysql的驱动jar包,依然可以通过编译。在运行阶段才会依赖驱动包。实际开发当中,我们应该做到编译不依赖,运行时才依赖。
上述代码产生的新问题,mysql驱动类的全限定类名作为一个字符串java类中是写死的,一旦发生改变,还需要修改源码。
使用配置文件结合反射就可以解决上述问题。
2、使用工厂模式解耦合
在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。
那么这个读取配置文件,创建和获取三层对象的类就是工厂。
三、SpringIOC机制的详解
(一)、IOC概述及作用
1、IOC的简介,设计思想
SpringIOC:IOC是Inversion of Control的缩写,多数书籍翻译成"控制反转"。
IOC这个概念简单来说就是把复杂系统分解成相互合作的对象,这些对象类通过封装以后,内部实现对外部透明的,从而降低了解决问题的复杂度,而且可以灵活的被重用和扩展。
IOC理论提出的观点大体是这样的:借助于第三方实现具有依赖关系的对象之间的解耦。
由于引进了中间位置"第三方",也就是IOC容器,使得A、B、C、D这四个对象没有了耦合关系,齿轮之间的传动全部依靠IOC容器,全部对象的控制权上交给IOC容器,所以IOC容器成了整个系统的关键核心,它起到一个"粘合剂"的作用,把系统中所有对象粘合在一起发挥作用。
当把图中的IOC容器拿掉,可以发现A、B、C、D这四个 对象之间没有耦合关系。这样的话,当我们去实现A的时候,根本无需去考虑其他几个对象,对象之间的依赖关系已经讲到了最低程度。
2、IOC作用
IOc本质上就是一个大工程,大容器。主要作用就是创建和管理对象的依赖关系,削减计算机程序的耦合(解除我们代码间的依赖关系),提高程序的可扩展性和可维护性。
(二)SpringIOC入门案例前期准备
1、构建maven工程,添加Spring框架的依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<!--导入spring的context坐标,context依赖core、beans、expression aop-->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
2、创建持久层接口和实现类
java
public interface UserDao {
public void save();
}
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("userDao save method running...... ");
}
}
3、创建业务层接口和实现类
java
public interface UserService {
public void saveService();
}
public class UserServiceImpl implements UserService {
@Override
public void saveService() {
System.out.println("userSerivce save method running......");
}
}
4、在resources文件夹中创建一个任意名称的xml文件
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/springbeans.xsd">
</beans>
5、让Spring管理资源,在配置文件中配置service和dao
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">
<!--配置userDaoImpl-->
<bean id="userDao" class="com.ujiuye.dao.impl.UserDaoImpl"></bean>
<!--配置userServiceImpl-->
<bean id="userService" class="com.ujiuye.service.UserServiceImpl"></bean>
</beans>
6、测试IOC配置是否成功
java
@Test
public void test1(){
//1:获得spring IOC容器对象:
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
//2:从容器当中根据id获得UserDaoImpl 对象,执行userDao对象的save方法
UserDao userDao = (UserDao) applicationContext.getBean("userDao");
userDao.save();
//3:从容器当中根据id获得UserServiceImpl 对象,执行userService对象的saveService
方法
UserService userService = (UserService)applicationContext.getBean("userService");
userService.saveService();
}
运行结果:
(三)Spring基于XML的IOC细节
1、IOC配置文件详解:配置标签书写规范,配置标签的属性
bean标签:用于配置对象交给Spring来创建
默认情况下他会调用类中无参数的构造器,如果没有无参数构造器则不能创建成功。
基本属性:
id:Bean实例对象在Spring容器中的唯一标识
class:Bean的全限定类名
2、SpringIOC机制源码解析
(1) IOC容器解析
IOC思想基于IOC容器完成,IOC容器底层就是对象工厂,Spring中工厂的类结构图如下;
1)BeanFactory:IOC容器的基本实现,是Spring内部使用的接口,不提供开发人员使用,加载配置文件时,不会创建对象,在获得(使用)对象时才采取创建对象。
2)HierarchicalBeanFactory:这个工厂接口非常简单,实现了Bean工厂的分层。工厂接口也是继承自BeanFactory,也是一个二级接口,相对于父接口,他只是扩展了一个重要的功能---------工厂分层
3)AutowireCapableBeanFactory:该接口有自动配置能力,需要注意的是,ApplicationContext接口并实现此接口,因为应用代码很少用到此功能,如果确实需要的话,可以调用ApplicationContext的getAutowireCapableBeanFactory方法,来获取此接口的实例
4)ListableBeanFactory:获取bean时,Spring鼓励使用这个接口定义的api,如查看bean
的个数、获取某一类型Bean的配置名、查看容器中是否包括某一Bean等方法。
5)ApplicationContext:BeanFactory接口的子接口,提供更多强大的功能,一般由开发人员使用。接口提供了bean基础性操作同时,扩展了国际化等功能。ApplicationContext接口在加载配置文件时候就会配置文件当中的对象进行创建,存放IOC容器当中
6)AnnotationConfigApplicationContext:当使用注解配置容器对象时,需要使用此类来创建spring容器。他用来读取注解。
7)ClassPathXmlApplicationContext:它是从类的根路径下加载配置文件
8)FileSystemXmlApplicationContext:它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置。
(2)IOC容器底层bean初始化过程
1)BeanFactionPostProcessor
作用:定义了在bean工厂对象创建后,bean对象创建前执行的动作,用于对工厂进行创建后业务处理
运行时机:操作用于对工厂进行处理,仅运行一次
- BeanPostProcessor
作用:定义了所有bean初始化前后进行的统一动作,用于对bean进行创建前业务处理与创建后业务处理
运行时机:当前操作伴随着每个bean的创建过程,每次创建bean均运行该操作。
3)InitializingBean
作用:定义了每个bean的初始化前进行的动作,属于非统一性动作,用于对bean进行创建前业务处理。
运行时机:当前操作伴随着任意一个bean的创建过程,保障其个性化业务处理
(四)手动实现自己的IOC容器
1、分析IOC实现思路
2、IOC原理实现---环境搭建:构建maven工程,引入依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
3、设计接口和类以及编写配置文件
java
//设定UserDao接口
public interface UserDao {
public void save();
}
//设定UserDao接口实现类
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("userDao save method running...... ");
}
}
配置文件:
XML
<!--配置userDaoImpl-->
<bean id="userDao" class="com.ujiuye.dao.impl.UserDaoImpl"></bean>
4、使用xml技术解析配置文件
XML
<!--引入dom4J-->
<dependency>
<groupId>dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>1.6.1</version>
</dependency>
java
/**
* 创建自己的工厂类
*/
public class MyBeanFactory {
//创建一个map集合,模拟IOC容器
private static Map<String,Object> map = new HashMap<>();
static{
try {
//使用dom4J 解析xml文件:
//第一步:获得一个解析器:
SAXReader reader = new SAXReader();
//第二: 读取外部的配置文件:
String path = "src/main/resources/applicationContext.xml";
//第三: 读取了整个文档对象
Document document = reader.read(path);
//第四: 获得根节点:
Element rootElement = document.getRootElement();//beans
//第五: 获得根节点下的所有的bean 标签对应的节点:
List<Element> bean = rootElement.elements("bean");// bean
for (Element element : bean) {
//获得id属性对应的值:
String id1 = element.attributeValue("id");
//获得class属性对应的值:【全限定类名】
String aClass = element.attributeValue("class");//获得class对应的值: 全限定类名。
//通过反射创建对象:
Class clz = Class.forName(aClass);
Object object = clz.newInstance();
//存容器 id做key,创建出来的对象value
map.put(id1,object);
}
} catch (Exception e) {
e.printStackTrace();
}
}
java
/**
* 根据id从容器当中获得对象
* @param id id的名称
* @return 返回Object类型对象
*/
public static Object getBean(String id){
Object o = map.get(id);
return o;
}
}
5、编写测试文件,展示测试结果
java
@Test
public void testMyFactory(){
//1:创建工厂对象
MyBeanFactory factory =new MyBeanFactory();
//2:从容器当中根据id获得对象
UserDao userDao = (UserDao) factory.getBean("userDao");
System.out.println(userDao);
userDao.save();
}
(五)Spring管理bean细节
1、bean实例化方式
(1)构造方法的方式
1)创建User类
java
public class User implements Serializable {
public User(){
System.out.println("user created...");
}
}
2)配置Spring容器管理user类型对象
html
<!--配置user对象-->
<bean id="user" class="com.ujiuye.pojo.User"></bean>
3)测试容器实例化User对象是否成功)
java
@Test
public void testUser(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
(2)静态工厂方式
1)创建静态工厂ItemFactory
java
public class ItemFactory {
//静态方法返回实例bean
public static User createUser(){
System.out.println("static method running create bean ......");
return new User();
}
}
2)配置Spring容器管理User类型对象
java
<!--静态工厂实例化对象-->
<bean id="user" class="com.ujiuye.factory.ItemFactory"></bean>
3)测试容器实例化User对象是否成功
java
@Test
public void testItemFactory(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
(3)实例工厂方式
1)创建实例工厂NewItemFactory
java
public class NewItemFactory {
//工厂的非静态方法返回bean实例
public User createUser(){
System.out.println("Dynamic method running create bean ......");
return new User();
}
}
2)配置Spring容器管理NewItemFactory类型对象
java
<!--实例工厂-->
<bean id="itemFactory" class="com.ujiuye.factory.NewItemFactory"></bean>
<bean id="user" factory-bean="itemFactory" factory-method="createUser"></bean>
3)测试容器实例化User对象是否成功
java
@Test
public void testNewItemFactory(){
ApplicationContext context =
new ClassPathXmlApplicationContext("applicationContext.xml");
User user = (User) context.getBean("user");
System.out.println(user);
}
2、bean作用域
(1)bean作用域介绍
所谓Bean的作用域其实就是指Spring给我们创建出的对象的存活范围,在配置文件中通过bean的scope属性指定
scope:指对象的作用范围,取值如下:
取值范围 | 说明 |
---|---|
singleton | 默认值,单例的 |
prototype | 多例的 |
request | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中 |
session | WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中 |
global session | WEB 项目中,应用在 Portlet 环境,如果没有 Portlet 环境那么globalSession 相当于 session |
(2)bean作用域的解析
1)当scope的取值为singleton时
Bean的实例化个数:1个
Bean的实例化时机:当Spring核心文件被加载时,实例化配置的Bean实例
2)当scope的取值为prototype时
Bean的实例化个数:多个
Bean的实例化时机:当调用getBean()方法时实例化Bean
3)当Scope的取值为其他值
scope指定为其他值,需要在特定的环境下使用。
四、Spring依赖注入(DI)
(一)介绍
定义
它是SpringBoot框架核心IOC的具体实现。组件之间的依赖关系由容器在应用系统运行期来决定,也就是由动态地将某种依赖关系的目标对象实例注入到应用系统中的各个关联的组件之中。
(二)注入方式
1、构造函数注入
(1)构建Account类,提供所有属性的构造方法
顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 Spring 框架来为我们注入。具体代码如下:
java
public class Account {
private String name;
private Integer age;
private Date birthday;
public Account(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
}
(2)配置Account类(使用构造方法注入依赖数据)
XML
<!--使用构造函数的方式:给account中的属性赋值
要求:
类中需要提供一个对应参数列表的构造器函数
涉及的标签:
constructor-arg:
属性:
name: 执行参数在构造器中的名称
value:它能赋的值是基本数据类型和 String 类型
ref:它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
-->
<bean id="now" class="java.util.Date"></bean>
<bean id="account" class="com.ujiuye.pojo.Account">
<constructor-arg name="name" value="王达"></constructor-arg>
<constructor-arg name="age" value="20"></constructor-arg>
<constructor-arg name="birthday" ref="now"></constructor-arg>
</bean>
(3)构造函数注入测试方法
java
@Test
public void testDI(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Account account = (Account) applicationContext.getBean("account");
System.out.println("name:"+account.getName()+" age:"+account.getAge()+"birthday:"+account.getBirthday());
//打印结果: name:王达 age:20 birthday:Thu Feb 04 13:53:45 CST 2021
}
2、setter注入
(1)修改Account类,添加属性的setter方法
java
public class Account {
private String name;
private Integer age;
private Date birthday;
public Account() {
}
public Account(String name, Integer age, Date birthday) {
this.name = name;
this.age = age;
this.birthday = birthday;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
(2)配置Account类(利用setter方法注入依赖数据)
html
<!--使用set方法的方式给属性赋值
涉及的标签: property
属性:
name:找的是类中set方法后面的部分
ref: 给属性赋值是其他bean类型的
value:给属性赋值是基本数据类型和 string 类型的
实际开发当中, 此种方式用的比较多,推荐使用
-->
<bean id="now" class="java.util.Date"></bean>
<bean id="account" class="com.ujiuye.pojo.Account">
<property name="name" value="张三丰"></property>
<property name="age" value="31"></property>
<property name="birthday" ref="now"></property>
</bean>
(3)setter注入测试方法
java
@Test
public void testDI(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Account account = (Account) applicationContext.getBean("account");
System.out.println("name:"+account.getName()+" age:"+account.getAge()+"birthday:"+account.getBirthday());
//测试结果: name:张三丰 age:31 birthday:Thu Feb 04 14:05:19 CST 2021
}
3、注入集合数据
(1)修改Account类,添加集合属性
给类中的集合成员传值,它用的也是set方法注入的方式,只不过变量的数据类型都是集合。
我们这里介绍注入数组,List,Set,Map,Properties。具体代码如下:
java
public class Account {
//注入数组,List集合,Set集合,Map集合,Properties集合属性
private String[] myStrs;
private List<String> myList;
private Set<String> mySet;
private Map<String,String> myMap;
private Properties myProps;
public Account() {
}
public String[] getMyStrs() {
return myStrs;
}
public void setMyStrs(String[] myStrs) {
this.myStrs = myStrs;
}
public List<String> getMyList() {
return myList;
}
public void setMyList(List<String> myList) {
this.myList = myList;
}
public Set<String> getMySet() {
return mySet;
}
public void setMySet(Set<String> mySet) {
this.mySet = mySet;
}
public Map<String, String> getMyMap() {
return myMap;
}
public void setMyMap(Map<String, String> myMap) {
this.myMap = myMap;
}
public Properties getMyProps() {
return myProps;
}
public void setMyProps(Properties myProps) {
this.myProps = myProps;
}
}
(2)配置Account类(利用setter注入依赖集合数据)
html
<!--注入集合类型数据:
涉及到标签:
List结构: array list set
Map结构: map entry props prop
-->
<bean id="account" class="com.ujiuye.pojo.Account">
<!--注意:在注入集合数据时,只要是结构相同,标签可以互换-->
<!--注入数组数据-->
<property name="myStrs">
<array>
<value>array-AAA</value>
<value>array-BBB</value>
<value>array-CCC</value>
</array>
</property>
<!--注入List集合数据-->
<property name="myList">
<list>
<value>list-AAA</value>
<value>list-BBB</value>
<value>list-CCC</value>
</list>
</property>
<!--注入Set集合数据-->
<property name="mySet">
<list>
<value>set-AAA</value>
<value>set-BBB</value>
<value>set-CCC</value>
</list>
</property>
<!--注入Map集合-->
<property name="myMap">
<map>
<entry key="map-a" value="AAA"></entry>
<entry key="map-b">
<value>BBB</value>
</entry>
</map>
</property>
<!--注入Properties集合-->
<property name="myProps">
<props>
<prop key="pro-a">AAA</prop>
<prop key="pro-b">BBB</prop>
</props>
</property>
</bean>
(3)集合注入测试方法
java
@Test
public void testDI(){
ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");
Account account = (Account) applicationContext.getBean("account");
System.out.println("array:"+ Arrays.toString(account.getMyStrs()));
System.out.println("list:"+account.getMyList());
System.out.println("set:"+account.getMySet());
System.out.println("map:"+account.getMyMap());
System.out.println("props:"+account.getMyProps());
/*
测试结果:
array:[array-AAA, array-BBB, array-CCC]
list:[list-AAA, list-BBB, list-CCC]
set:[set-AAA, set-BBB, set-CCC]
map:{map-a=AAA, map-b=BBB}
props:{pro-b=BBB, pro-a=AAA}
*/
}
五、Spring配置文件模块化
(一)Spring模块化的介绍
我们现在的配置都集中配在了一个applicationContext.xml文件中,当开发人员过多时, 如果所有bean 都配置到同一个配置文件中,会使这个文件巨大,而且也不方便维护。 针对这个问题,Spring提供了多配置文件的方式,也就是所谓的配置文件模块化。
(二)Spring模块化的配置
1、Spring模块化配置方式一
并列的多个配置文件直接编写多个配置文件,比如beans1.xml,beans2.xml,...,然后在创建ApplicationContext的时候,直接传入多个配置文件。
java
ApplicationContext act = new ClassPathXmlApplicationContext("beans1.xml","beans2.xml","...");
2、Spring模块化配置方式二
主从配置文件,先配置一个主配置文件,然后在里面导入其他的配置文件。
java
<import resource="beans1.xml" />
<import resource="beans2.xml" />
注意:
同一个xml文件中不能出现相同名称的bean,如果出现会报错。''
多个xml文件如果出现相同的名称的bean,不会报错,但是后加载的会覆盖前加载的bean,所以企业开发中尽量保证bean的名称是唯一的。
六、模块设计模式解析
(一)模块化设计模式介绍
模块方法(Template Method)模式的定义:定义一个操作中的算法骨架,而将算法的一些步骤延迟到子类中,使得子类可以不改变该算法结构的情况下重定义该算法的某些特定步骤。它是一种类行为型模式。
模块方法模式的静态结构图:
(二)模板设计模式的应用场景
在多个子类中拥有相同的方法,而且逻辑相同时,可以将这些方法抽出来发到一个模块抽象类中程序主框架相同,细节不同的情况下,也可以使用模块方法。
举例说明:
- 使用过Servlet的人都清楚,除了要在web.xml做相应的配置外,还需继承一个叫HttpServlet的抽象类。HttpService类提供了一个service()方法,这个方法调用七个do方法中的一个或几个,完成对客户端调用的响应。这些do方法需要由HttpServlet的具体子类提供,因此这是典型的模板方法模式。
- 持久层对数据库的操作,Spring提供了JdbcTemplate模板对象, 完成CRUD操作。
(三)模板设计模式实现
1、需求:用模块方法模式实现出国留学手续设计程序
**分析:**出国留学手续一般经过以下流程:索取学校资料,提出入学申请,办理因私出国护照、出境卡和公证,申请签证,体检、订机票、准备行装,抵达目标学校等,其中有些业务对各个学校是一样的,但有些业务因学校不同而不同,所以比较适合用模板方法模式来实现。
在本实例中,我们先定义一个出国留学的抽象类 StudyAbroad,里面包含了一个模板方法 TemplateMethod(),该方法中包含了办理出国留学手续流程中的各个基本方法,其中有些方法的处理由于各国都一样,所以在抽象类中就可以实现,但有些方法的处理各国是不同的,必须在其具体子类(如美国留学类 StudyInAmerica)中实现。如果再增加一个国家,只要增加一个子类就可以了。
2、代码实现
抽象类:
java
//抽象类: 出国留学
public abstract class StudyAbroad {
//定义抽象方法,索取学校资料
public abstract void LookingForSchool();
//定义抽象方法,定义入学申请
public abstract void ApplyForEnrol();
//定义入学申请方法:
public void ApplyForPassport() {
System.out.println("三.办理因私出国护照、出境卡和公证:");
System.out.println(" 1)持录取通知书、本人户口簿或身份证向户口所在地公安机关申请办理因私出国护照和出境卡。");
System.out.println(" 2)办理出生公证书,学历、学位和成绩公证,经历证书,亲属关系公证,经济担保公证。");
}
//定义申请签证方法
public void ApplyForVisa() {
System.out.println("四.申请签证:");
System.out.println(" 1)准备申请国外境签证所需的各种资料,包括个人学历、成绩单、工作经历的证明;个人及家庭收入、资金和财产证明;家庭成员的关系证明等;");
System.out.println(" 2)向拟留学国家驻华使(领)馆申请入境签证。申请时需按要求填写有关表格,递交必需的证明材料,缴纳签证。有的国家(比如美国、英国、加拿大等)在申请签证时会要求申请人前往使(领)馆进行面试。");
}
//体检、订机票、准备行装 方法
public void ReadyGoAbroad() {
System.out.println("五.体检、订机票、准备行装:");
System.out.println(" 1)进行身体检查、免疫检查和接种传染病疫苗;");
System.out.println(" 2)确定机票时间、航班和转机地点。");
}
//定义抵达抽象方法
public abstract void Arriving();
}
子类
java
//定义具体的子类,美国留学
public class StudyInAmerica extends StudyAbroad {
//索取资料的具体实现
@Override
public void LookingForSchool() {
System.out.println("一.索取学校以下资料:");
System.out.println(" 1)对留学意向国家的政治、经济、文化背景和教育体制、学术水平进行较为全面的了解;");
System.out.println(" 2)全面了解和掌握国外学校的情况,包括历史、学费、学制、专业、师资配备、教学设施、学术地位、学生人数等;");
System.out.println(" 3)了解该学校的住宿、交通、医疗保险情况如何;");
System.out.println(" 4)该学校在中国是否有授权代理招生的留学中介公司?");
System.out.println(" 5)掌握留学签证情况;");
System.out.println(" 6)该国政府是否允许留学生合法打工?");
System.out.println(" 8)毕业之后可否移民?");
System.out.println(" 9)文凭是否受到我国认可?");
}
//入学申请的具体实现
@Override
public void ApplyForEnrol() {
System.out.println("二.入学申请:");
System.out.println(" 1)填写报名表;");
System.out.println(" 2)将报名表、个人学历证明、最近的学习成绩单、推荐信、个人简历、托福或雅思语言考试成绩单等资料寄往所申请的学校;");
System.out.println(" 3)为了给签证办理留有充裕的时间,建议越早申请越好,一般提前1年就比较从容。");
}
//抵达的具体实现
@Override
public void Arriving() {
System.out.println("六.抵达目标学校:");
System.out.println(" 1)安排住宿;");
System.out.println(" 2)了解校园及周边环境。");
}
}
java
public class StudyAbroadProcess {
public static void main(String[] args) {
StudyAbroad tm = new StudyInAmerica();
tm.TemplateMethod();
}
}
七、Spring整合JDBC实现用户的CRUD
(一)整合思路分析
Spring提供了ioc容器,管理jdbc操作数据库的过程中需要的数据库连接对象,同时Spring提供了整合jdbc操作数据库的工具类JdbcDaoSupport 和模板工具 JdbcTemplate,在JdbcTemplate中提供了大量的操作数据库的方式供用户使用。所以我们只需要获取模板工具类然后调用方法就可以完成Jdbc的操作了。
(二)构建maven工程,添加技术依赖
XML
<dependencies>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--导入C3P0连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--导入junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
(三)构建数据库表并编写实体类Account
java
public class Account implements Serializable {
private Integer id;
private String name;
private double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
(四)编写持久层代码AccountDao以及实现类AccountDaoImpl
java
public interface AccountDao {
public void save(Account account);
public void delete(Integer id);
public void update(Account account);
public Account findById(Integer id);
public Integer getTotalRecords();
public List<Account> findAll();
}
java
public class AccountDaoImpl implements AccountDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void save(Account account) {
String sql ="insert into account(name,money) values(?,?)";
jdbcTemplate.update(sql,account.getName(),account.getMoney());
}
@Override
public void delete(Integer id) {
String sql ="delete from account where id = ? ";
jdbcTemplate.update(sql,id);
}
@Override
public void update(Account account) {
String sql ="update account set money = ? , name=? where id= ?";
jdbcTemplate.update(sql,account.getMoney(),account.getName(),account.getId());
}
@Override
public Account findById(Integer id) {
String sql ="select * from account where id = ? ";
Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class),id);
return account;
}
@Override
public Long getTotalRecords() {
Long count = jdbcTemplate.queryForObject("select count(*) from account",Long.class);
System.out.println(count);
return count;
}
@Override
public List<Account> findAll() {
String sql ="select * from account";
List<Account> accountList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
return accountList;
}
}
(五)编写业务层代码Accountservice以及实现类AccountServiceImpl
java
public interface AccountService {
public void save(Account account);
public void delete(Integer id);
public void update(Account account);
public Account findById(Integer id);
public Long getTotalRecords();
public List<Account> findAll();
}
java
public class AccountServiceImpl implements AccountService {
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void save(Account account) {
accountDao.save(account);
}
@Override
public void delete(Integer id) {
accountDao.delete(id);
}
@Override
public void update(Account account) {
accountDao.update(account);
}
@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}
@Override
public Long getTotalRecords() {
return accountDao.getTotalRecords();
}
@Override
public List<Account> findAll() {
return accountDao.findAll();
}
}
(六)创建并编写配置文件:配置容器管理对象
将数据库的连接信息抽取到外部配置文件中,和spring的配置文件分离开,有利于后期维护
java
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test
jdbc.username=root
jdbc.password=root
XML
<!--数据源对象-->
<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>
<!--配置JdbcTemplate模板对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--配置AccountDaoImpl对象-->
<bean id="accountDao" class="com.ujiuye.dao.impl.AccountDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--配置AccountServiceImpl对象-->
<bean id="accountService" class="com.ujiuye.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
(七)测试代码
java
//测试save方法
@Test
public void testJdbcTemplateSave(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
Account account = new Account();
account.setName("jack");
account.setMoney(1001D);
service.save(account);
}
//测试update方法
@Test
public void testJdbcTemplateUpdate(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
Account account = new Account();
account.setName("jack2");
account.setMoney(999D);
account.setId(1008);
service.update(account);
}
//测试delete方法
@Test
public void testJdbcTemplateDelete(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
service.delete(1001);
}
//测试唯一性查询findById
@Test
public void testJdbcTemplateFindById(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
Account account = service.findById(1001);
System.out.println(account);
}
//测试总记录数
@Test
public void testJdbcTemplateGetTotalRecords(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
Long totalRecords = service.getTotalRecords();
System.out.println("表当中的总记录数为:"+totalRecords);
}
//测试账户列表
@Test
public void testJdbcTemplateGetAll(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
AccountService service = (AccountService) context.getBean("accountService");
List<Account> accountList = service.findAll();
accountList.forEach((account -> {
System.out.println(account);
}));
}
八、常用注解
(一)创建对象的注解
|-------------|------------------------|
| 注解 | 说明 |
| @Component | 使用在类上用于实例化Bean |
| @Controller | 使用在web层类上用于实例化Bean |
| @Service | 使用在service层类上用于实例化Bean |
| @Repository | 使用在dao层类上用于实例化Bean |
注意:
- 使用注解进行开发时,需要在applicationContext.xml中配置组件扫描,作用是指定哪个包及其子包下的Bean需要进行扫描一便识别使用注解配置的类、字段和方法。
XML
<!--注解的组件扫描-->
<context:component-scan base-package="com.ujiuye"></context:component-scan>
- 使用@Component或@Repository标识UserDaoImpl需要Spring进行实例化。
java
//@Component("userDao")
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Override
public void save() {
System.out.println("save running... ...");
}
}
- 使用@Component或Service标识UserServiceImpl需要Spring进行实例化
java
//@Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {
@Override
public void save() {
System.out.println("save running... ...");
}
}
(二)用于注入数据的注解
|------------|-------------------------------------------------------------------------------------------------------------------|
| 注解 | 说明 |
| @Value | 注入普通属性 |
| @Autowired | 自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。 |
| @Qualifier | 结合@Autowired一起使用用于根据名称进行依赖注入 |
| @Resource | 相当于@Autowired+@Qualifier,按照名称进行注入 |
- 使用@Value进行字符串的注入
java
@Repository("userDao")
public class UserDaoImpl implements UserDao {
@Value("注入普通数据")
private String str;
@Value("${jdbc.driver}")
private String driver;
@Override
public void save() {
System.out.println(str);
System.out.println(driver);
System.out.println("save running... ...");
}
}
- 使用Autowired或者@Autowired+@Qulifier或者@Resource进行userDao的注入
java
//@Component("userService")
@Service("userService")
public class UserServiceImpl implements UserService {
/*@Autowired
@Qualifier("userDao")*/
@Resource(name="userDao")
private UserDao userDao;
@Override
public void save() {
userDao.save();
}
}
(三)用于改变作用范围的注解
|--------|----------------------------------------------------------------------|
| 注解 | 说明 |
| @Scope | 标注Bean的作用范围,scope取值singleton prototype request session globalsession |
使用@Scope标注Bean的范围
java
//@Scope("prototype")
@Scope("singleton")
public class UserDaoImpl implements UserDao {
//此处省略代码
}
九、基于注解的IOC案例
(一)构建工程,添加依赖
XML
<dependencies>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--导入C3P0连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--导入junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
(二)使用注解配置
java
@Repository(value = "userDao")
public class UserDaoImpl implements UserDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void save(User user) {
String sql ="insert into user (name,birthday) values(?,?)";
jdbcTemplate.update(sql,user.getName(),user.getBirthday());
}
}
java
@Service("userService")
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao ;
@Override
public void saveService(User user) {
userDao.save(user);
}
}
(三)创建配置文件并开启对注解的支持
XML
<context:component-scan base-package="com.offcn"></context:component-scan>
<!--配置数据源-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
</property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg name="dataSource" ref="dataSource"></constructor-arg>
</bean>
(四)编写测试代码
java
//IOC 注解版本的案例:
@Test
public void test1(){
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
User user = new User();
user.setName("admin");
user.setBirthday(new Date());
userService.saveService(user);
}
十、纯注解配置
(一)新注解说明
|-----------------|----------------------------------------------------------------------------------------------------|
| 注解 | 说明 |
| @Configuration | 用于指定当前类是一个Spring配置类,当创建容器时会从该类上加载注解 |
| @ComponentScan | 用于指定Spring在初始化容器时要扫描的包。作用和在Springdexml配置文件中的<context:component-scan base-package="com.offcn"/>一样 |
| @Bean | 使用在方法上,标注将该方法的返回值存储到Spring容器中 |
| @PropertySource | 用于加载xxx.properties文件中的配置 |
| @Import | 用于导入其他配置类 |
- @Configuration,@ComponentScan,@Import
java
@Configuration //指定当前类是一个配置类,取代applicationContext.xml配置文件
@ComponentScan("com.offcn") //指定Spring在初始化容器时要扫描的包
@Import({xxx.class}) //导入其他的配置类
public class SpringConfiguration {
...
}
- @PropertySource,@Value
java
@PropertySource("classpath:dbConfig.properties") //当前类中引入dbConfig.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
java
@Bean(name="dataSource") //将方法的返回值存入到IOC容器当中
public DataSource getDataSource() throws Exception {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
(二)通过注解获取容器
java
public void AnnotationTest (){
//使用AnnotationConfigApplicationContext获得ioc容器对象
ApplicationContext context= new AnnotationConfigApplicationContext(SpringConfig.class);
Object obj = context.getBean("...");
}
十一、Spring整合Junit
(一)IOC测试类中的问题和解决思路
在测试类中,每个测试方法都有一下两行代码:
java
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService userService = (UserService) context.getBean("userService");
这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。
(二)整合Junit配置步骤
1、添加技术依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<!--此处需要注意的是,spring5 及以上版本要求 junit 的版本必须是 4.12 及以上-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--导入Spring整合Junit的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入spring的context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
2、使用@RunWith注解替换原有运行器
java
@RunWith(SpringJUnit4ClassRunner.class)
public class SpringJunitTest {
}
3、使用@ContextConfiguration 指定 Spring 配置文件的位置
java
@RunWith(SpringJUnit4ClassRunner.class)
//加载spring核心配置文件
//@ContextConfiguration(value = {"classpath:applicationContext.xml"})
//加载spring核心配置类
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
}
4、使用@Autowired 给测试类中的变量注入数据
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfiguration.class})
public class SpringJunitTest {
@Autowired
private ApplicationContext context;
@Test
public void test(){
...
}
}
十二、Spring整合DButils实现转账业务
(一)添加转账方法,并演示事务问题
1、转账Account表以及对应的实体bean
java
public class Account implements Serializable {
private Integer id;
private String name;
private double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
2、引入DBUtils依赖坐标
XML
<!--引入QueryRunner-->
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</version>
</dependency>
3、创建Account接口和对应实现类
java
public interface AccountDao {
public Account findByName(String name);
public void update(Account account);
}
java
public class AccountDaoImpl implements AccountDao {
private QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
@Override
public Account findByName(String name) {
try {
String sql ="select *from account where name =? ";
Account account = queryRunner.query(sql, new BeanHandler<> (Account.class), name);
return account;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
@Override
public void update(Account account) {
try {
String sql ="update account set money =? where name =? ";
queryRunner.update(sql, account.getMoney(), account.getName());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
4、创建AccountService接口和实现类
java
public interface AccountService {
/**
* 定义业务方法, 实现转账
* @param sourceAccountName 来源账户
* @param targetAccountName 目标账户
* @param money 转账金额
*/
public void transfer(String sourceAccountName,String targetAccountName,double money);
}
java
public class AccountServiceImpl implements AccountService {
//依赖dao层
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String sourceAccountName, String targetAccountName, double money) {
//查询来源账户和目标账户
Account sAccount = accountDao.findByName(sourceAccountName);
Account tAccount = accountDao.findByName(targetAccountName);
//来源账户减钱,目标账户加钱
sAccount.setMoney(sAccount.getMoney()-money);
tAccount.setMoney(tAccount.getMoney()+money);
//持久化到数据库
accountDao.update(sAccount);
//模拟异常发生
int i=1/0;
accountDao.update(tAccount);
}
}
5、测试转账业务
java
@Test
public void test1(){
//获得业务层对象:
AccountService service = new AccountServiceImpl();
service.transfer("aaa","bbb",100D);
}
(二)编写ConnectionUtils工具类管理数据库连接
java
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 connection = tl.get();
//2.判断当前线程上是否有连接
if (connection == null) {
//3.从数据源中获取一个连接,并且存入ThreadLocal中
connection = dataSource.getConnection();
tl.set(connection);
}
//4.返回当前线程上的连接
return connection;
}catch (Exception e){
throw new RuntimeException(e);
}
}
/**
* 把连接和线程解绑
*/
public void removeConnection(){
tl.remove();
}
(三)编写事务管理工具类
java
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
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.removeConnection();
connectionUtils.getThreadConnection().close();//还回连接池中
}catch (Exception e){
e.printStackTrace();
}
}
}
(四)编写业务层和持久层控制代码并配置Spring的IOC
java
public class AccountDaoImpl implements AccountDao {
private QueryRunner queryRunner ;
private ConnectionUtils connectionUtils;
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
@Override
public Account findByName(String name) {
try {
String sql ="select * from account where name =? ";
Account account = queryRunner.query(connectionUtils.getThreadConnection(), sql,new BeanHandler<Account>(Account.class), name);
return account;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
@Override
public void update(Account account) {
try {
String sql ="update account set money =? where name =? ";
queryRunner.update(connectionUtils.getThreadConnection(),sql,account.getMoney(),account.getName());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
java
public class AccountServiceImpl implements AccountService {
//依赖dao层
private AccountDao accountDao ;
private TransactionManager txManager;
public AccountDao getAccountDao() {
return accountDao;
}
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
public TransactionManager getTxManager() {
return txManager;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
@Override
public void transfer(String sourceAccountName, String targetAccountName,double money) {
try {
//开启事务:
txManager.beginTransaction();
//查询来源账户和目标账户
Account sAccount = accountDao.findByName(sourceAccountName);
Account tAccount = accountDao.findByName(targetAccountName);
//来源账户减钱,目标账户加钱
sAccount.setMoney(sAccount.getMoney()-money);
tAccount.setMoney(tAccount.getMoney()+money);
//持久化到数据库
accountDao.update(sAccount);
//模拟异常发生
int i=1/0;
accountDao.update(tAccount);
} catch (Exception exception){
//事务回滚
txManager.rollback();
exception.printStackTrace();
} finally {
//释放资源
txManager.release();
}
}
}
XML
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
</property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器-->
<bean id="txManager" class="com.offcn.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置QueryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"scope="prototype"></bean>
<!--配置Dao对象-->
<bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
<!-- 注入QueryRunner -->
<property name="queryRunner" ref="queryRunner"></property>
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!-- 配置Service层对象 -->
<bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
<!-- 注入dao -->
<property name="accountDao" ref="accountDao"></property>
<!--注入txManager-->
<property name="txManager" ref="txManager"></property>
</bean>
(五)测试转账
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestAccountTransfer {
@Autowired
private AccountService accountService;
@Test
public void test1(){
accountService.transfer("aaa","bbb",100);
}
}
(六)转账业务案例中存在的问题
(七)使用动态代理实现事务控制
1、创建代理工具类
java
/**
* 用于创建客户业务层对象工厂(当然也可以创建其他业务层对象,只不过我们此处不做那么繁琐)
*/
public class BeanFactory {
private AccountService accountService;
private TransactionManager txManager;
public void setAccountService(AccountService accountService) {
this.accountService = accountService;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
/**
* 获取Service代理对象
* @return
*/
public AccountService getAccountService() {
return (AccountService)
Proxy.newProxyInstance(accountService.getClass().getClassLoader(),accountService.getClass().getInterfaces(),new InvocationHandler() {
/**
* 添加事务的支持
* @param proxy
* @param method 执行目标方法被封装到Method当中
* @param args 执行目标方法的参数
* @return 执行目标方法的返回值
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returntValue = null;
try {
//1.开启事务
txManager.beginTransaction();
//2.执行操作
returntValue = method.invoke(accountService, args);
//3.提交事务
txManager.commit();
//4.返回结果
return returntValue;
} catch (Exception e) {
//5.回滚操作
txManager.rollback();
throw new RuntimeException(e);
} finally {
//6.释放连接
txManager.release();
}
}
});
}
}
2、配置并测试动态代理转账业务
配置静态工厂,生产实例bean
XML
<!--配置beanfactory-->
<bean id="beanFactory" class="com.offcn.factory.BeanFactory">
<!-- 注入service -->
<property name="accountService" ref="accountService"></property>
<!-- 注入事务管理器 -->
<property name="txManager" ref="txManager"></property>
</bean>
<!--配置代理的service-->
<bean id="proxyAccountService" factory-bean="beanFactory" factorymethod="getAccountService">
</bean>
业务层代码的修改:把和事务相关的代码去掉
java
@Override
public void transfer(String sourceAccountName, String targetAccountName, double money) {
Account sAccount = accountDao.findByName(sourceAccountName);
Account tAccount = accountDao.findByName(targetAccountName);
//来源账户减钱,目标账户加钱
sAccount.setMoney(sAccount.getMoney()-money);
tAccount.setMoney(tAccount.getMoney()+money);
//持久化到数据库
accountDao.update(sAccount);
//模拟异常发生
//int i=1/0;
accountDao.update(tAccount);
}
测试代码:
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestAccountTransfer {
@Autowired
@Qualifier("proxyAccountService")
private AccountService accountProxyService;
@Test
public void testAccountProxy(){
accountProxyService.transfer("aaa","bbb",100D);
}
}
**总结:**使用动态代理改造之后,业务层代码已经和事务相关代码进行了分离,并且保证了事务的一致性。
十三、SpringAOP机制详解
(一)AOP概述
1、什么是AOP
AOP为Aspect Oriented Programming的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
2、AOP编程思想
AOP面向切面编程是一种编程思想,是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各个部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
3、Spring中的常用术语
- Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在Spring中,这些点指的是方法,因为Spring只支持方法类型的连接点。
- Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。
- Advice(通知/增强):通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
- Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下,Introduction可以在运行期为类动态地添加一些方法。
- Target(目标对象):代理的目标对象。
- Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。
- Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。Spring采用动态代理织入,而Aspect采用编译期织入和类装载期织入。
- Aspect(切面):是切入点和通知(引介)的结合。
4、AOP编程底层的实现机制
(1)JDK动态代理
只提供接口的代理,不支持类的代理。JDK动态代理通过反射来接受被代理的类,并且要求被代理的类必须实现一个接口。JDK动态代理的核心是invocationHandler接口和Proxy类
(2)CGLB动态代理
通过继承的方式来实现动态代理,因此如果某个类被标记为final,那么他是无法使用CGLB做动态代理。
5、AOP的作用及优势
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强。
优势:减少重复代码,提高开发效率,并且便于维护。
(二)Spring基于XML的AOP配置
1、环境搭建
(1)添加依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
(2)添加代码
准备实体类
java
public class Account implements Serializable {
private Integer id;
private String name;
private double money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
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{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
创建AccountDao接口以及实现类
java
public interface AccountDao {
public Account findByName(String name);
public void update(Account account);
}
java
public class AccountDaoImpl implements AccountDao {
private QueryRunner queryRunner = new QueryRunner(new ComboPooledDataSource());
@Override
public Account findByName(String name) {
try {
String sql ="select *from account where name =? ";
Account account = queryRunner.query(sql, new BeanHandler<>(Account.class), name);
return account;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
@Override
public void update(Account account) {
try {
String sql ="update account set money =? where name =? ";
queryRunner.update(sql, account.getMoney(), account.getName());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
创建AccountService接口以及实现类
java
public interface AccountService {
/**
* 定义业务方法, 实现转账
* @param sourceAccountName 来源账户
* @param targetAccountName 目标账户
* @param money 转账金额
*/
public void transfer(String sourceAccountName,String targetAccountName,double money);
}
java
public class AccountServiceImpl implements AccountService {
//依赖dao层
private AccountDao accountDao = new AccountDaoImpl();
@Override
public void transfer(String sourceAccountName, String targetAccountName,double money) {
//查询来源账户和目标账户
Account sAccount = accountDao.findByName(sourceAccountName);
Account tAccount = accountDao.findByName(targetAccountName);
//来源账户减钱,目标账户加钱
sAccount.setMoney(sAccount.getMoney()-money);
tAccount.setMoney(tAccount.getMoney()+money);
//持久化到数据库
accountDao.update(sAccount);
//模拟异常发生
int i=1/0;
accountDao.update(tAccount);
}
}
(3)创建配置文件并导入约束
XML
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns: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">
</beans>
(4)配置Spring的IOC
html
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
</property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!-- 配置Connection的工具类 ConnectionUtils -->
<bean id="connectionUtils" class="com.offcn.utils.ConnectionUtils">
<!-- 注入数据源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--配置queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner"></bean>
<!--配置accountDao-->
<bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
<property name="queryRunner" ref="queryRunner"></property>
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
<!--配置accountService-->
<bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
(5)抽取公共代码制作成通知
java
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
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.removeConnection();
connectionUtils.getThreadConnection().close();//还回连接池中
}catch (Exception e){
e.printStackTrace();
}
}
}
2、AOP配置步骤
(1)配置bean标签
XML
<!--配置通知:txManager-->
<bean id="txManager" class="com.offcn.utils.TransactionManager">
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
(2)使用aop:config 声明AOP配置
aop:config:
作用: 开始声明aop配置
XML
<aop:config>
<!-- 配置的代码都写在此处 -->
</aop:config>
(3)使用aop:aspect 配置切面
aop:aspect
作用 : 用于配置切面
属性: id :给切面提供一个唯一标识。
ref:引用配置好的通知类 bean 的 id。
XML
<aop:aspect id="tdAdvice" ref="txManager">
<!--配置通知的类型要写在此处-->
</aop:aspect>
(4)使用aop:pointcut配置切入点表达式
aop:pointcut
作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
属性: expression:用于定义切入点表达式。
id:用于给切入点表达式提供一个唯一标识
XML
<aop:pointcut id="point1" expression="execution( public void com.offcn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.St
ring,java.lang.Double))"/>
(5)使用aop:xxx配置对应的通知类型
aop:before
作用:用于指定通知类中的增强方法名称
属性:method:用于指定通知类中的增强方法名称
ponitcut-ref:用于指定切入点的表达式的引用
poinitcut:用于指定切入点表达式
**执行时间点:**切入点方法执行之前执行
XML
<aop:before method="beginTransaction" pointcut-ref="point1"></aop:before>
aop:after-returning
作用:用于配置后置通知
属性:method:指定通知中方法的名称
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:切入点方法正常执行之后。它和异常通知只能有一个执行。
XML
<aop:after-returning method="commit" pointcut-ref="point1"/>
aop:after-throwing
作用:用于配置异常通知
属性:method:指定通知中方法的名称
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:切入点方法执行产生异常后执行。它和后置通知只能执行一个。
XML
<aop:after-throwing method="rollback" pointcut-ref="point1"/>
aop:after
作用:用于配置最终通知
属性:method:指定通知中方法的名称
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
执行时间点:无论切入点方法执行时是否有异常,它都会在其后面执行。
XML
<aop:after method="release" pointcut-ref="point1"/>
3、切入点表达式说明
切入点表达式的语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
- 访问修饰符可以省略
- 返回值类型、包名、类名、方法名可以使用*代表任意
- 包名与类名之间一个点。代表当前包下的类,两个点...表示当前包及其子包下的类
- 参数列表可以使用两个点...表示任意个数,任意类型的参数列表
4、环绕通知配置事务管理
在TransactionManager类当中添加方法
java
/**
* 环绕通知:
* spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参
数。
* 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
* @param pjp
* @return
*/
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object returnValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
returnValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return returnValue;
}
aop:around:
**作用:**用于配置环绕通知
**属性:**method:指定通知中方法的名称
pointct:定义切入点表达式
pointcut-ref:指定切入点表达式的引用
**说明:**它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
注意:通常情况下,环绕通知都是独立使用的。
XML
<aop:config>
<aop:aspect id="tdAdvice" ref="txManager">
<aop:pointcut id="point1" expression="execution(*com.offcn.service.impl.*.*(..))"/>
<!-- 配置环绕通知 -->
<aop:around method="transactionAround" pointcut-ref="point1"/>
</aop:aspect>
</aop:config>
(三)Spring基于注解的AOP配置
1、环境搭建
(1)构建maven工程添加AOP注解的相关依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
(2)导入代码
沿用上一章节资源
copy Account AccountDao AccountDaoImpl AccountService AccountServiceImpl
(3)在配置文件中导入context的名称空间且配置
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: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/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">
</beans>
(4)资源使用注解配置
XML
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
</property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--配置queryRunner-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
</bean>
(5)在配置文件中指定spring要扫描的包
XML
<!-- 告知 spring,在创建容器时要扫描的包 -->
<context:component-scan base-package="com.offcn"></context:component-scan>
2、配置步骤
(1)通知类使用注解配置
java
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("txManager")
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
}
(2)通知类使用@Aspect注解声明为切面
java
/**
* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
*/
@Component("txManager")
@Aspect //表明当前类是一个切面类
public class TransactionManager {
@Autowired
private ConnectionUtils connectionUtils;
}
(3)增强方法上使用注解配置通知
- @Before
作用:把当前方法看成是前置通知
属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。
java
//开启事务
@Before("execution(* com.offcn.service.impl.*.*(..)))")
public void beginTransaction(){
try {
System.out.println("before..........................");
connectionUtils.getThreadConnection().setAutoCommit(false);
}catch (Exception e){
e.printStackTrace();
}
}
- @AfterReturning
作用:把当前方法看成是后置通知。
**属性:**value:用于指定切入点表达式,还可以指定切入点表达式的引用
java
// 提交事务
@AfterReturning("execution(* com.offcn.service.impl.*.*(..)))")
public void commit(){
try {
connectionUtils.getThreadConnection().commit();
}catch (Exception e){
e.printStackTrace();
}
}
- @AfterThrowing
作用:把当前方法看成是异常通知。
属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用
java
//回滚事务
@AfterThrowing("execution(* com.offcn.service.impl.*.*(..)))")
public void rollback(){
try {
connectionUtils.getThreadConnection().rollback();
}catch (Exception e){
e.printStackTrace();
}
}
- @After
作用:把当前方法看成是最终通知。
属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用 。
java
//释放连接
@After("execution(* com.offcn.service.impl.*.*(..)))")
public void release(){
try {
connectionUtils.removeConnection();
connectionUtils.getThreadConnection().close();//还回连接池中
}catch (Exception e){
e.printStackTrace();
}
}
(4)开启Spring对注解AOP的支持
java
<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy/>
(5)环绕通知注解配置
@Around
作用:把当前方法看成是环绕通知。
属性:value:用于指定切入点表达式,还可以指定切入点表达式的引用。
java
// 环绕通知:
@Around("execution(*com.offcn.service.impl.*.*(..))")
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object returnValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
returnValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return returnValue;
}
(6)切入点表达式注解
java
@Pointcut("execution(* com.offcn.service.impl.*.*(..))")
private void point1() {}
// 环绕通知:
@Around("point1()")///注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
//定义返回值
Object returnValue = null;
try {
//获取方法执行所需的参数
Object[] args = pjp.getArgs();
//前置通知:开启事务
beginTransaction();
//执行方法
returnValue = pjp.proceed(args);
//后置通知:提交事务
commit();
}catch(Throwable e) {
//异常通知:回滚事务
rollback();
e.printStackTrace();
}finally {
//最终通知:释放资源
release();
}
return returnValue;
}
十四、Spring事务详解
(一)Spring基于XML的事务配置
1、环境搭建
(1)构建工程,添加依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--导入C3P0连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
(2)创建Spring的配置文件,导入约束
XML
<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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 告知 spring,在创建容器时要扫描的包 -->
<context:component-scan base-package="com.offcn"></context:component-scan>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
</property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
(3)沿用转账业务
copy Account AccountDao AccountDaoImpl AccontService AccountServiceImpl 代码:
注意: AccountDaoImpl 具体使用使用Spring提供的JdbcTemplate模板对象实现
java
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public Account findByName(String name) {
String sql ="select * from account where name =? ";
Account account = this.jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), name);
return account;
}
@Override
public void update(Account account) {
String sql ="update account set money =? where name =? ";
this.jdbcTemplate.update(sql, account.getMoney(), account.getName());
}
}
2、事务管理配置步骤
(1)配置事务管理器
html
<!--平台事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
(2)配置事务的通知引用事务管理器
html
<!--事务的配置-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>
(3)配置事务的属性
html
<!--
指定方法名称:是业务核心方法
read-only:是否是只读事务。默认 false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为:-1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
-->
<tx:attributes>
<tx:method name="*"/>
</tx:attributes>
(4)配置AOP切入点表达式
html
<!--事务的aop增强-->
<aop:config>
<aop:pointcut id="myPointcut" expression="execution(*com.offcn.service.impl.*.*(..))"/>
</aop:config>
(5)配置切入点表达式和事务通知的对应关系
html
<!--在aop:config标签内部:建立事务的通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>
(二)Spring基于注解的事务配置
1、环境搭建
(1)构建maven工程,添加相关依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--导入C3P0连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
</dependencies>
(2)创建Spring配置文件
XML
<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/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 告知 spring,在创建容器时要扫描的包 -->
<context:component-scan base-package="com.offcn"></context:component-scan>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
</property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
(3)沿用转账业务的代码:dao实现类和service实现类采用注解的形式,添加到容
器中管理
copy Account AccountDao AccountImpl AccountService AccountServiceImpl 到工程当中复用
2、事务管理配置步骤
(1)配置事务管理器并注入数据源
XML
<!-- 配置事务管理器 -->
<bean id="transactionManager"class="org.springframework.jdbc.datasource.DataSourceTran
sactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
(2)在业务层使用@Transactional注解
java
/**
该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
出现接口上,表示该接口的所有实现类都有事务支持。
出现在类上,表示类中所有方法有事务支持
出现在方法上,表示方法有事务支持。
以上三个位置的优先级:方法>类>接口
*/
@Service("accountService")
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {
//依赖dao层
@Autowired
private AccountDao accountDao ;
@Transactional(readOnly = false,propagation = Propagation.REQUIRED)
@Override
public void transfer(String sourceAccountName, String targetAccountName,Double money) {
Account sAccount = accountDao.findByName(sourceAccountName);
Account tAccount = accountDao.findByName(targetAccountName);
//来源账户减钱,目标账户加钱
sAccount.setMoney(sAccount.getMoney()-money);
tAccount.setMoney(tAccount.getMoney()+money);
//持久化到数据库
accountDao.update(sAccount);
//模拟异常发生
//int i=1/0;
accountDao.update(tAccount);
}
}
(3)配置文件中开启Spring对注解事务的支持
XML
<!-- 开启 spring 对注解事务的支持 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
十五、Spring整合Mybatis实现用户的CRUD
(一)整理思路分析
Mybatis框架是一个持久层ORM框架,而Spring则是一个综合性一站式框架。所以整合是Mybatis往Spring上整合。就是让Spring框架接管Mybatis的组件。
Mybatis 单独运行时,数据源的管理,事务的管理, SqlSessionFactory 以及接口的实现类都是 Mybatis管理的,整合后以上组件交给Spring 管理。
(二)构建maven工程,添加依赖
XML
<properties>
<spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
<!--导入junit单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<!--导入spring的context坐标-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Jdbc模块依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<!--导入Mysql 驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<!--导入C3P0连接池-->
<dependency>
<groupId>com.mchange</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.5.2</version>
</dependency>
<!--aop-->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.7</version>
</dependency>
<!--mybatis-Spring适配包 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.0</version>
</dependency>
<!-- mybatis orm框架 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
</dependencies>
(三)构建数据库表并创建实体类
java
create table User(
id int primary key auto_increment,
name varchar(32) not null,
address varchar(32) not null,
birthday date
);
java
public class User implements Serializable {
private Integer id;
private String name;
private String address;
private Date birthday;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", address='" + address + '\'' +
", birthday=" + birthday +
'}';
}
}
(四)编写dao层的接口UserMapper
java
public interface UserMapper {
int insert(User user);
int update(User user);
int delete(Integer id);
User findById(Integer id);
List<User> findAll();
}
(五)构建mapper接口对应的sql配置文件
XML
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.offcn.mapper.UserMapper">
<resultMap id="BaseResultMap" type="com.offcn.pojo.User">
<id column="id" jdbcType="INTEGER" property="id" />
<result column="name" jdbcType="VARCHAR" property="name" />
<result column="address" jdbcType="VARCHAR" property="address" />
<result column="birthday" jdbcType="DATE" property="birthday" />
</resultMap>
<insert id="insert" parameterType="com.offcn.pojo.User">
insert into user (name, birthday, address)
values (#{name}, #{birthday},#{address})
</insert>
<update id="update">
update user
set
name= #{name},
birthday=#{birthday},
address = #{address}
where id=#{id}
</update>
<delete id="delete">
delete from user
where id =#{id}
</delete>
<select id="findById" resultMap="BaseResultMap">
select * from user where id=#{id}
</select>
<select id="findAll" resultMap="BaseResultMap">
select * from user
</select>
</mapper>
(六)构建服务层接口UserService
java
public interface UserService {
int insert(User user);
int update(User user);
int delete(Integer id);
User findById(Integer id);
List<User> findAll();
}
(七)构建服务层实现类UserServiceImpl
java
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public int insert(User user) {
int num = userMapper.insert(user);
return num;
}
@Override
public int update(User user) {
int num = userMapper.update(user);
return num;
}
@Override
public int delete(Integer id) {
int num = userMapper.delete(id);
return num;
}
@Override
public User findById(Integer id) {
User user = userMapper.findById(id);
return user;
}
@Override
public List<User> findAll() {
List<User> userList = userMapper.findAll();
return userList;
}
}
(八)构建配置文件
XML
<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"
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/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
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!--开启包扫描-->
<context:component-scan base-package="com.offcn"> </context:component-scan>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--连接数据库的必备信息-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test">
</property>
<property name="user" value="root"></property>
<property name="password" value="root"></property>
</bean>
<!--Mybatis 核心对象: 工厂对象-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--工厂创建必须注入一个数据源-->
<property name="dataSource" ref="dataSource"/>
<!--指定mapper文件位置-->
<property name="mapperLocations" value="classpath:com/offcn/mapper/*Mapper.xml"></property>
<!--引入Mybatis的核心配置文件:如果Mybaits的核心配置要保留,需要再此处配置:-->
<property name="configLocation" value="classpath:SqlMapConfig.xml"/>
<!--别名配置-->
<property name="typeAliasesPackage" value="com/offcn/pojo"/>
<!--进行分页插件的配置-->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<value>
helperDialect=MySQL
reasonable=true
supportMethodsArguments=true
params=count=countSql
autoRuntimeDialect=true
</value>
</property>
</bean>
</array>
</property>
</bean>
<!--配置接口的扫描-->
<bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定了包: 能够将包下的接口生成实现类: -->
<property name="basePackage" value="com.offcn.mapper"></property>
</bean>
<!--配置平台管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--注入数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
(九)测试代码
java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class TestAccountTransfer {
@Autowired
private UserService userService;
//save
@Test
public void testInsert(){
User user = new User();
user.setName("admin");
user.setAddress("china");
user.setBirthday(new Date());
int num = userService.insert(user);
System.out.println("num:"+num);
}
//update
@Test
public void testUpdate(){
User user = new User();
user.setName("marry");
user.setAddress("America");
user.setBirthday(new Date());
user.setId(1);
int num = userService.update(user);
System.out.println("num:"+num);
}
//delete:
@Test
public void testDelete(){
int num = userService.delete(1);
System.out.println("num:"+num);
}
//findById
@Test
public void testFindById(){
User user = userService.findById(2);
System.out.println("user:"+user);
}
//findAll
@Test
public void testFindByAll(){
List<User> userList = userService.findAll();
System.out.println("userList:"+userList);
}
}