最新更新
相关代码已上传到Gitee,欢迎start!!
https://gitee.com/the_efforts_paid_offf/java-web-learning-code.git
前言
上回为各位分享了《六万字最全总结Java数据库编程MyBatis》没有学过的同学,建议先学MyBatis,这次在此基础上我们来学习经典框架之Spring!
❤️爆肝六万字最全总结Java数据库编程MyBatis(建议收藏)
相关资料:
1. Spring概述
1.1 介绍
Spring框架是企业使用最多的框架,没有之一。Spring是一站式框架,称之为一站式框架的原因是Spring可以整合其他框架。
要学习Spring的内容如下:
l Spring IoC:对象工厂及依赖注入;
l Spring AOP:面向切面编程技术,为Spring事务管理打下基础。
l Spring Transaction management:Spring事务管理。
l Spring Web MVC(不包含在本课程内,后面单独学习):简称Spring MVC框架,用来简化JavaWEB开发,当使用Spring MVC框架后,就不用再编写Servlet了。也就不再需要itcast-tools工具中BaseServlet类了。
l Spring与其他框架整合:因为我们只学习过MyBatis框架,所以当前我们只学习Spring整合MyBatis框架。
2. IoC入门
2.1 什么是IoC
Spring IoC的核心如下:
l 工厂负责对象生命周期的管理;(spring管理创建与销毁)
l 对象的依赖由工厂完成注入。(spring维护对象间关系)
Spring提出了对象工厂的概念,由Spring工厂来管理对象的生命周期。所谓对象生命周期指的是从对象的创建一直到对象的销毁都由Spring来管理。我们无需再自己new对象,而是从Spring工厂中获取需要的对象。甚至对象的依赖也由工厂来注入,无需手动注入依赖。
Spring工厂是ApplicationContext接口,通常我们使用的是AnnotationConfigApplicationContext类。其中Spring工厂内部是通过Map类型来维护的。
key | value |
“userDao1” | UserDao实例 |
“userService1” | UserService实例 |
… | … |
当我们需要获取工厂中的实例时,只需要调用工厂的getBean(“id”)即可。
@Test public void test3() { AnnotationConfigApplicationContext context = ... UserDao userDao = (UserDao) context.getBean("userDao1"); ... }
2.2 IoC入门案例1(基础案例)
入门案例1 使用IOC的方式创建UserDao对象 调用查询所有方法
思路:
- 目标类上加@Component
创建UserDao类, 书写方法 findAll ,在类上添加注解@component(”名字”),用来告知spring可以通过指定名字来创建该UserDao对象
- 配置类添加@Configuration 和 @ComponentScan
创建配置类 [SpringConfiguration,类上添加@Configuration注解和@ComponentScan注解.[(作用是用来告知Spring该类是配置类,并要扫描的包有哪些)]
- 测试类通过ApplicationContext的getBean(“名字”)获取对象
案例步骤如下:
l 配置类(SpringConfiguration)
l UserDao类
l UserDaoTest类
2.2.1 下载Spring
下载地址:
http://repo.springsource.org/libs-release-local/org/springframework/spring
解压:(Spring目录结构:)
docs :API和开发规范.
libs :jar包和源码.
schema :约束.
我们上课使用的maven,使用老师发的pom文件即可.
Pom文件也可以从本文档结尾的附录的pom01-Spring入门拷贝
2.2.2 配置类(SpringConfiguration)
任何Spring项目都建议创建配置类,它提供了Spring工厂最基本的配置信息。本案例中该类没有任何内容,只需要添加两个注解。
SpringConfiguration.java
package com.czxy.comfig; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @ComponentScan(basePackages = {"com.czxy.dao"}) @Configuration public class SpringConfiguration { }
l 其中@Configuration注解告知Spring当前类是一个配置类;
l 其中@componentScan注解告知Spring要扫描的包,Spring会扫描并加载指定包下所有类中的注解。
2.2.3 UserDao.java
我们需要在编写UserDao类,同时希望Spring去创建该类实例并添加到工厂中。这需要在类上添加@Component注解,同时指定实例的id。Spring会扫描到UserDao类上的@Component注解。
UserDao.java
package com.czxy.dao; import org.springframework.stereotype.Component; @Component("ud") public class UserDao { public void findAll(){ System.out.println("查询所有"); } }
2.2.4 UserDaoTest测试类
在测试类中我们需要先创建工厂对象,然后从工厂对象中获取UserDao对象实例。
UserDaoTest.java
package com.czxy.test; import com.czxy.comfig.SpringConfiguration; import com.czxy.dao.UserDao; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.AnnotationConfigApplicationContext; public class TestA { public static void main(String[] args) { ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserDao userDao = (UserDao) applicationContext.getBean("ud");// new UserDao(); userDao.findAll(); } }
2.3 IoC入门案例2(依赖注入)
入门案例2中我们需要创建UserService类,但我们知道UserServce一定会依赖UserDao类。然后我们让Spring工厂帮助我们完成依赖注入。
步骤如下:
l 定义UserService类并添加@Component注解;
l 在UserService类中添加private UserDao userDao依赖;
l 在userDao成员上添加@Resource注解指定依赖。
SpringConfiguration.java
package com.czxy.comfig; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @ComponentScan(basePackages = {"com.czxy.dao","com.czxy.service"}) @Configuration public class SpringConfiguration { }
UserService.java
package com.czxy.service; import com.czxy.dao.UserDao; import org.springframework.stereotype.Component; import javax.annotation.Resource; @Component("us") public class UserService { @Resource(name = "ud") private UserDao userDao ;//= new UserDao(); public void findAllUsers(){ System.out.println("开始查找"); userDao.findAll(); System.out.println("查找结束"); } }
测试类
@Test public void test02(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserService userService = (UserService) applicationContext.getBean("us"); userService.findAllUsers(); }
测试结果:
2.4 IoC入门案例3(面向接口编程)
入门案例3提供dao的接口和实现类、提供service的接口和实现类,程序之间使用接口,将所有实现类交予spring管理,完成程序间的解耦。
l 步骤如下
n 编写UserDao接口和实现类,并在实现类中添加@Component注解
n 编写UserService接口和实现类,并在实现类中添加@Component注解
n 在userDao成员变量中添加@Resource注解,注入dao的实现类。
n 编写配置类
n 编程测试类
l dao接口和实现类
public interface UserDao { public void findAll(); } @Component("userDaoImpl") public class UserDaoImplA implements UserDao { public void findAll(){ System.out.println("A方式 查询所有"); } }
l service接口和实现类
public interface UserService { public void findAllUsers(); }
@Component("userServiceImpl") public class UserServiceImplA implements UserService { @Resource(name = "userDaoImpl") private UserDao userDao ;//= new UserDao(); public void findAllUsers(){ System.out.println("开始查找"); userDao.findAll(); System.out.println("查找结束"); } }
l 配置类 不变
@ComponentScan(basePackages = {"com.czxy.dao","com.czxy.service"}) @Configuration public class SpringConfiguration { }
l 测试类
@Test
@Test public void test03(){ ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class); UserService userService = (UserService) applicationContext.getBean("userServiceImpl"); userService.findAllUsers(); }
测试结果:
2.5 IoC入门案例4(整合JUnit4)
l 修改测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={SpringConfigruation.class}) public class UserDaoTest { @Resource(name="userService1") private UserService userService; @Test public void testFindAll(){ userService.findAll(); } }
3. IoC详解
3.1 Bean的创建
前面已经学习了创建Bean的注解@Component,Spring还提供了一些衍生注解。
注解 | 描述 |
@Component | 将修饰的资源交予spring管理。value属性:为资源命名(唯一标识) |
@Controller | 衍生注解,与@Component作用和属性相同。特用于修饰表示层的资源。 |
@Service | 衍生注解,与@Component作用和属性相同。特用于修饰业务逻辑层的资源。 |
@Repository | 衍生注解,与@Component作用和属性相同。特用于修饰数据访问层的资源。 |
示例: 分别使用不同名字的注解来设置响应的类
@Repository("userDao1") public class UserDaoImpl implements UserDao { public void findAll(){ System.out.println("入门案例"); } } @Service("userService1") public class UserServiceImpl implements UserService{ @Resource(name="userDao1") private UserDao userDao; public void findAll(){ System.out.println("user service ..."); userDao.findAll(); } }
l 以上4个注解修饰的类,我们通常称为注册bean。目的是将某类的实例对象,添加到spring容器中。
3.2 依赖注入(DI)
注解 | 描述 | 修饰位置 |
@Resource(name=”…”) | 按照指定名称注入对象 | 字段、setter方法 |
@ Resource | 按照类型注入对象 | 字段、setter方法 |
@Value | 注入简单值 | 字段、setter方法、参数 |
@PropertySource | 加载properties配置文件 | 类 |
3.2.1 按照名称注入
示例: 按照名称来注入userDao对象
dao @Repository("userDao1") public class UserDaoImpl implements UserDao { public void findAll(){ System.out.println("入门案例"); } } service @Service("userService1") public class UserServiceImpl implements UserService{ @Resource(name="userDao1") private UserDao userDao; public void findAll(){ System.out.println("user service ..."); userDao.findAll(); } }
3.2.2 按照类型注入
示例: 分别创建dao ,service ,和测试类,按照类型注入对应的对象
dao @Repository public class UserDaoImpl implements UserDao { public void findAll(){ System.out.println("入门案例"); } } service @Service public class UserServiceImpl implements UserService{ @Resource private UserDao userDao; public void findAll(){ System.out.println("user service ..."); userDao.findAll(); } } 测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={SpringConfigruation.class}) public class UserDaoTest { @Resource private UserService userService; @Test public void testFindAll(){ userService.findAll(); } }
3.2.3 普通数据注入
示例:字符串类型的成员变量和方法参数注入数据.
固定值注入 public class SpringConfigruation { @Value("com.mysql.jdbc.Driver") private String driver; @Value("jdbc:mysql://127.0.0.1:3306/crm_ssm_v1_0") public void setUrl(String url){ System.out.println("字段:" + driver); System.out.println("方法:" + url); } } 测试 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={SpringConfigruation.class}) public class UserDaoTest { @Test public void testFindAll() throws SQLException{ } }
4 properties数据注入
需求: 把properties文件中的数据读取出来,在测试类中展示
总体思路:
使用@PropertySource加载properties配置文件,“classpath:”固定前缀,表示从类路径下加载配置文件。
@Value(${jdbc.driver}) 获得配置文件中指定key的内容
默认:将整个表达式进行注入,及 driver变量的值是 ${jdbc.driver}
固定代码:必须配置PropertySourcesPlaceholderConfigurer实例。
项目结构如下:
jdbc.properties配置文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test1
jdbc.username=root
jdbc.password=1234
配置类 添加内容 package com.czxy.demo02; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; @Configuration @PropertySource("classpath:db.properties") public class SpringConfig02 { // 在4.2.4版本读取properties必须写,必须要写的固定格式 @Bean public static PropertySourcesPlaceholderConfigurer create(){ return new PropertySourcesPlaceholderConfigurer(); } } 测试类: package com.czxy.demo02; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig02.class) public class TestB { @Value("${jdbc.driver}") private String driverClassName; @Value("${jdbc.url}") private String url; @Test public void test01(){ System.out.println("driverClassName="+driverClassName); System.out.println("url="+url); } }
测试结果:正常获取数据
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SIWE7Gtr-1633402543916)(C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps7DBD.tmp.jpg)]
1.1 @Bean(工厂Bean)
把别人创建的类,交给IOC管理可以使用方法配合注解@Bean的方式实现.
通过@Component等注解,将我们自己编写的对象配置到spring容器中。
通过@Resource等注解,将我们自己编写的对象之间的关系配置到spring容器中。
实际开发中,我们经常会遇到某些类不是我们写的,此时我们希望通过IOC对这种类进行管理,我们就没法办在这个类上加@Component等注解了. 这个时候可以创建一个方法在方法上使用@Bean来实现对这些类对象的管理
3.2.5 基本使用:类型注入
@Bean用于修饰方法 ,将方法创建的对象添加到spring容器
需求: 假设UserDao不是我们写的类,无法使用@Component注解,
创建一个方法用于获取UserDao对象
准备如下几个类
UserDao: package com.czxy.demo03; public class UserDao { public void findAll(){ System.out.println("查询所有 "); } } SpringConfig03: package com.czxy.demo03; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration public class SpringConfig03 { @Bean public UserDao getUserDao(){ return new UserDao(); } } 测试类: package com.czxy.demo03; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig03.class) public class TestC { @Resource private UserDao userDao; @Test public void test01(){ userDao.findAll(); } }
测试结果:
3.2.6 基本使用:指定名称注入
上面例子中,在方法上只是书写了一个@Bean.并没有给期产生的对象命名如果想命名可以通过如下方式.
@Bean(name=”名字”) 可以为当前对象设置一个名称,如果没有使用name设置名称,默认名为“ 方法名 ”。
需求:
UserDao是个接口,有俩实现类UserDaoImplA和UserDaoImplB, 这三个类假设我们都不能修改.
现在想获取这俩实现类的对象,交给IOC管理. 设计完成该例子.
项目结构如下:
UserDao接口: package com.czxy.demo04; public interface UserDao { public void findAll(); } UserDaoImplA实现类: package com.czxy.demo04; public class UserDaoImplA implements UserDao { @Override public void findAll() { System.out.println("A 方式实现查询所有用户 "); } } UserDaoImplB实现类: package com.czxy.demo04; public class UserDaoImplB implements UserDao { @Override public void findAll() { System.out.println("B 方式实现查询所有用户 "); } } 配置类: package com.czxy.demo04; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class SpringConfig04 { @Bean(name ="userDaoImplA" ) public UserDao getUserDaoA(){ return new UserDaoImplA(); } @Bean(name ="userDaoImplB" ) public UserDao getUserDaoB(){ return new UserDaoImplB(); } } 测试类: package com.czxy.demo04; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig04.class) public class TestD { @Resource(name = "userDaoImplB") private UserDao userDao; @Test public void test01(){ userDao.findAll(); } }
测试结果:
userDaoImplB对应:
userDaoImplA对应:
3.2.7 依赖注入:引用类型
当某一个方法的参数是一个被IOC管理的对象时,可以通过@Bean的方式,自动注入该对象.
如UserDao对象被IOC管理了. 那么 若有方法 形如testXXX(UserDao userDao) 则可以在方法上添加@Bean,来自动注入UserDao对象.
示例1:
把UserDao交给IOC
在配置类中书写一个方法show(UserDao userDao),完成自动注入,在测试类中进行测试.
代码详情: UserDao package com.czxy.demo06; import org.springframework.stereotype.Repository; @Repository public class UserDao { public void findAll(){ System.out.println("查询所有"); } } 配置类:SpringConfig06 package com.czxy.demo06; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import javax.annotation.Resource; @Configuration @ComponentScan(basePackages = "com.czxy.demo06") public class SpringConfig06 { @Bean public String show(UserDao userDao){ System.out.println("完成自动注入:"+userDao); //测试调用userDao方法 userDao.findAll(); return null; } } 测试类: package com.czxy.demo06; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfig06.class) public class TestA1 { @Test public void test02(){ //无需书写任何代码, 单纯执行 //该方法就会看到自动注入UserDao } }
测试结果:
可以看到 完成了自动注入.
示例2:
把UserDao对象存放到spring容器中, 然后再UserService方法中要使用UserDao,此时可以直接把UserDao当做参数传递到方法中
`
配置类: package com.czxy.demo02.config; import com.czxy.demo02.dao.UserDao; import com.czxy.demo02.dao.UserDaoImpl; import com.czxy.demo02.service.UserService; import com.czxy.demo02.service.UserServiceImpl; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; @Configuration @ComponentScan(basePackages = {"com.czxy.demo02"}) public class SpringConfiguration { @Bean public UserDao getUserDao(){ return new UserDaoImpl(); } @Bean public UserService getUserService(UserDao userDao){ System.out.println(userDao); return new UserServiceImpl(); } } 测试类 package com.czxy.demo02.test; import com.czxy.demo02.config.SpringConfiguration; import com.czxy.demo02.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class TestA { @Resource public UserService userService; @Test public void test01(){ System.out.println(userService); } }
测试结果:
3.2.8 依赖注入:简单类型
需求: 把properties文件中的字符串 以简单类型的方式添加到参数中 打印对应的值.
l 配置类
相应代码:
@PropertySource("classpath:db.properties") public class SpringConfiguration2 { @Bean public static PropertySourcesPlaceholderConfigurer create(){ return new PropertySourcesPlaceholderConfigurer(); } @Bean public UserService createUserService(@Value("${jdbc.username}") String name, @Value("${jdbc.password}") String pwd){ System.out.println("name = "+name+" pwd ="+pwd); return new UserServiceImpl(); } }
l 测试类
代码:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration2.class) public class TestB { @Resource private UserService userService; @Test public void test01(){ System.out.println(userService); } }
测试结果:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3KBzWRNK-1633402543928)(C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps7DD8.tmp.jpg)]
1.1 Bean的作用域
通过@Scope可以Bean的作用域,也就是通知spring是否每次都创建新对象。
注解 | 描述 | 取值 |
@Scope | 用于设置Bean的作用域 | singleton :默认值,单例的.prototype :多例的. |
单例模式: 整个IOC容器中只有该实体类的一个对象
多例模式: 整个IOC容器中该实体类有多个对象
示例:
搞一个User类,在配置类中设置单例模式和多例模式,创建两个对象观察效果.
配置类:
测试类:
单例模式结果: 地址编号相同,说明是用的同一个对象
保持测试类不变,只更改为多例模式
测试结果:打印的两个地址编号不同,说明IOC容器中有多个对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WWrI1INl-1633402543932)(C:\Users\ADMINI~1\AppData\Local\Temp\ksohtml\wps7DEF.tmp.jpg)]
l 其他取值(了解)
- request :WEB项目中,Spring创建一个Bean的对象,将对象存入到request域中.
- session :WEB项目中,Spring创建一个Bean的对象,将对象存入到session域中.
- globalSession :WEB项目中,应用在Portlet环境.如果没有Portlet环境那么globalSession相当于session
3.3 生命周期
生命周期指 单实例 对象由创建到销毁的整个过程。
此处,主要研究初始化方法和销毁方法。
3.3.1 实例Bean
实例Bean同时2个注解,来控制类中那个方法初始化方法,那个方法是销毁方法。
注解 | 描述 |
@PostConstruct | 初始化方法,项目启动时执行,只会被调用一次。 |
@PreDestroy | 销毁方法,项目关闭时执行,只会被调用一次。 |
实例Dog @Component public class Dog { @PostConstruct public void init(){ System.out.println("狗 初始化"); } public void eat(){ System.out.println("狗 吃吃吃.."); } @PreDestroy public void destory(){ System.out.println("狗 销毁 "); } } 配置类 @ComponentScan(basePackages={"com.czxy.domain"}) public class SpringConfigruation { } 测试类 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class TestC { @Resource private Dog dog; @Test public void test01(){ dog.eat(); } }
测试结果 :
3.3.2 工厂Bean
工厂Bean通过@Bean的2个属性完 成初始化和销毁方法的配置。
initMethod:配置初始化方法
destroyMethod:配置销毁方法
实体类: public class Cat { public void init(){ System.out.println("猫 初始化"); } public void eat(){ System.out.println("猫 吃吃吃.."); } public void destory(){ System.out.println("猫 销毁 "); } } 配置类 @Configuration @ComponentScan(basePackages = {"com.czxy.demo02"}) public class SpringConfiguration { @Bean(initMethod = "init" ,destroyMethod = "destory") public Cat getCat(){ return new Cat(); } } 测试类: @Resource private Cat cat; @Test public void test02(){ cat.eat(); }
测试结果:
4. AOP
4.1 AOP概述
4.1.1 什么是AOP
AOP:全称是Aspect Oriented Programming即:面向切面编程。
面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用代理的技术,在不修改原来代码的基础上,对已有方法进行增强。
4.1.2 快速入门1
需求:
User类中有方法eat . 现在在不修改eat方法的前提下,对eat进行增强. 增强的内容是,在执行eat之前,先执行 沐浴更衣 这个动作.
实现思路:
\1. 搭建基本的测试 可以在测试类中执行eat方法
\2. 创建切面类,指明对eat方法进行增强;
具体代码如下: 在书写代码之前记得在pom文件添加spring相关的依赖.
User类 @Component public class User { public void eat(){ System.out.println("吃吃吃"); } } 配置类: @Configuration //设置为 配置类 @ComponentScan(basePackages = {"com.czxy.demo01"}) // 设置要扫描的包 @EnableAspectJAutoProxy // 设置 开启切面 public class SpringConfig01 { } 切面类: @Component @Aspect public class MyAspect01 { @Before("execution(public void com.czxy.demo01.User.eat())") public void bf01(){ System.out.println("沐浴更衣 "); } } 测试类: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes ={SpringConfig01.class}) public class TestA { @Resource private User user; @Test public void test01(){ user.eat(); } }
执行结果:
4.1.3 AOP作用和优势
l 作用:
\4. 在程序运行期间,不修改源码对已有方法进行增强。
l 优势:
\5. 减少重复代码
\6. 提高开发效率
\7. 维护方便
4.1.4 快速入门2
需求: 使用AOP 对UserService接口的两个方法进行增强. 在方法执行之前,开启事务,在方法执行之后关闭事务.
项目结构:
具体代码:
Pom中需要添加aop相关的依赖
UserService接口: 接口中提供两个方法 public interface UserService { public void addUser(); public void delUser(); } 实现类UserServiceImpl @Service public class UserServiceImpl implements UserService { @Override public void addUser() { System.out.println("添加用户 "); } @Override public void delUser() { System.out.println("删除用户"); } } 配置类: @Configuration @ComponentScan(basePackages = "com.czxy.demo02") @EnableAspectJAutoProxy public class SpringConfiguration2 { } 切面类MyAspect02: @Component @Aspect public class MyAspact02 { @Before("execution(public void com.czxy.demo02.UserService.*())") public void bf(){ System.out.println("开启事务"); } @After("execution(public void com.czxy.demo02.UserService.*())") public void af(){ System.out.println("关闭事务"); } } 测试类: @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration2.class) public class TestB { @Resource private UserService userService; @Test public void test01(){ userService.addUser(); System.out.println("----------"); userService.delUser(); } }
测试结果:
4.1.5 快速入门3
需求: 使用AOP 对UserService和BookService接口的方法进行增强.
4.1.6 AOP实现方法
Spring AOP 主要通过2种代理技术来实现:动态代理、CGLIB
动态代理:用于对接口+实现类情况进行代理。
@EnableAspectJAutoProxy(proxyTargetClass = false )
CGLIB:用于对仅有类情况进行代理。
@EnableAspectJAutoProxy(proxyTargetClass = true )
4.2 相关AOP术语
Target( *目标对象 *):
代理的目标对象。通俗点讲:你需要增强的类,这个类就是目标对象
例如:UserServiceImpl
Joinpoint( *连接点 *):
所谓连接点是指可能被增强的位置。在spring中,AOP是对方法进行增强,这个位置/时机可以是方法前或者方法后,或者出异常的时候。
例如:addUser()方法执行之前的位置 或者 addUser()方法执行之后的位置 或者 AddUser出异常的时候
Pointcut( *切入点 *):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。通俗讲: 确定了在哪个位置进行增强
例如:@Before(“execution(public void com.czxy.demo02.UserService.addUser())”)
Advice( *通知 */ *增强 *): 具体要干的事情
所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
例如:bf()、af()
Aspect( *切面 *):
是切入点和通知的结合。
例如:MyAspect类
Proxy *(代理) *:
一个类被AOP增强后,就产生一个结果代理类。
4.3 相关注解
注解 | 描述 | |
@Aspect | 把当前类声明成切面类 | |
@Before | 把当前方法看成是前置通知 | |
@AfterReturning | 把当前方法看成是后置通知。 | |
@AfterThrowing | 把当前方法看成是异常通知 | |
@After | 把当前方法看成是最终通知 | |
@Around | 把当前方法看成是环绕通知 | |
@Pointcut | 指定切入点表达式 |
4.3.1 切入点表达式
execution: 匹配方法的执行(常用) execution(表达式) 表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数)) 写法说明: 全匹配方式: public void com.itheima.service.impl.CustomerServiceImpl.saveCustomer() 访问修饰符可以省略 void com.itheima.service.impl.CustomerServiceImpl.saveCustomer() 返回值可以使用*号,表示任意返回值 * com.itheima.service.impl.CustomerServiceImpl.saveCustomer() 包名可以使用*号,表示任意包,但是有几级包,需要写几个* * *.*.*.*.CustomerServiceImpl.saveCustomer() 使用..来表示当前包,及其子包 * com..CustomerServiceImpl.saveCustomer() 类名可以使用*号,表示任意类 * com..*.saveCustomer() 方法名可以使用*号,表示任意方法 * com..*.*() 参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数 * com..*.*(*) 参数列表可以使用..表示有无参数均可,有参数可以是任意类型 * com..*.*(..) 全通配方式: * *..*.*(..)
4.3.2 通知方法
l 方式1:没有参数形式
@Before("execution(public void com.czxy.demo01.User.eat())") public void bf01(){ System.out.println("洗手 "); }
执行效果:
l 方式2:获得参数JoinPoint,从而获得目标类,目标方法等信息
@Before("execution(public void com.czxy.demo01.User.eat())") public void bf01(JoinPoint jp){ System.out.println("洗手 "); System.out.println("目标类:"+jp.getTarget());//获取目标类 System.out.println("切入点:"+jp.getSignature());//获取切入点 }
执行效果:
l 方式3:环绕通知获得参数ProceedingJoinPoint,对目标方法的执行进行控制。
@Around("execution(public void com.czxy.demo01.User.eat())") public void ar(ProceedingJoinPoint jp) throws Throwable { System.out.println("洗手 "); jp.proceed();//执行 eat方法 System.out.println("擦嘴"); }
执行效果:
4.3.3 抽取公共 切入点
使用@PointCut可以将公共的切入点进行抽取,一般都声明在私有方法上。
在通知注解使用,通过方法名引用。
@Pointcut("execution(* com.czxy.service..*.*(..))") private void myPointcut(){ } @Before("myPointcut()") public void bf(JoinPoint joinPoint){ System.out.println("前置..." + joinPoint.getTarget()); System.out.println("前置..." + joinPoint.getSignature().getName()); } @AfterReturning("myPointcut()") public void af(){ System.out.println("后置..."); }
4.4 完整通知演示
4.4.1 AOP编程
l 编写需要对目标类,增量的类和方法(可以复制)
@Pointcut("execution(* com.czxy.service..*.*(..))") private void myPointcut(){ } @Before("myPointcut()") public void bf(JoinPoint joinPoint){ System.out.println("前置..." + joinPoint.getTarget()); System.out.println("前置..." + joinPoint.getSignature().getName()); } @AfterReturning("myPointcut()") public void af(){ System.out.println("后置..."); }
l 测试
4.4.2 目标接口和类
接口 public interface UserService { public void saveUser(); public String updateUser(); } 实现类 @Service public class UserServiceImpl implements UserService { @Override public void saveUser() { System.out.println("save"); } @Override public String updateUser() { System.out.println("update"); return "abc"; } }
4.4.3配置类 @ComponentScan(basePackages={"com.czxy"}) @EnableAspectJAutoProxy public class SpringConfigruation { } 4.4.4切面类 @Component @Aspect public class MyAspect2 { @Pointcut("execution(* com.czxy.service..*.*(..))") private void myPointcut(){ } @Before("myPointcut()") public void bf(JoinPoint joinPoint){ System.out.println("前置..." + joinPoint.getTarget()); System.out.println("前置..." + joinPoint.getSignature().getName()); } @AfterReturning(value="myPointcut()",returning="ret") public void ar(JoinPoint joinPoint , Object ret){ System.out.println("后置..." + ret); } @Around("myPointcut()") public void ar(ProceedingJoinPoint joinPoint) throws Throwable{ System.out.println("环绕前"); joinPoint.proceed(); //执行目标方法 System.out.println("环绕后"); } @AfterThrowing(value="myPointcut()",throwing="ex") public void at(JoinPoint joinPoint ,Throwable ex){ System.out.println("异常:" + ex.getMessage()); } @After("myPointcut()") public void af(){ System.out.println("最终"); } } 4.4.5测试类 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes={SpringConfigruation.class}) public class UserDaoTest { @Resource private UserService userService; @Test public void testFindAll() throws SQLException{ userService.saveUser(); int i = 1/0; userService.updateUser(); } }
5. 整合MyBatis
之前使用的持久层框架是MyBatis, 现在将Mybatis框架整合到Spring框架中,由Spring统一管理.其核心思路是把Mapper对应的实现类对象存放在IOC容器中.
需求:
通过Spring+Mybatis整合,把一个用户的信息添加到数据库中.
实现步骤:
- 环境搭建
- 编写Dao和Service
- 书写配置类
- 测试
完整的项目结构如下:
5.1 环境搭建
5.1.1 导入 pom相关的依赖
<properties> <spring.version>4.2.4.RELEASE</spring.version> </properties> <dependencies> <!-- spring start --> <!--spring core start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!--spring core end--> <!--spring aop start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <!--spirng aop end--> <!--spring aspects start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <!--spring aspects end--> <!--spring instrumentation start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>${spring.version}</version> </dependency> <!--spring instrumentation end--> <!--spring messaging start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency> <!--spring messaging end--> <!--spring data access start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <!--spring data access end--> <!--spring web start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc-portlet</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!--spring web end --> <!--spring test start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!--spring test end --> <!-- spring end --> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>tk.mybatis</groupId> <artifactId>mapper</artifactId> <version>3.5.2</version> </dependency> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>3.7.5</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.4.5</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.47</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>1.3.2</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency> </dependencies>
5.1.2 配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/test1
jdbc.username=root
jdbc.password=1234
5.1.3 创建数据库和表
CREATE TABLE USER( uid VARCHAR(32) PRIMARY KEY, username VARCHAR(50), PASSWORD VARCHAR(32) )
5.1.4 创建domain
public class User { @Id private String uid; private String username; private String password; //getter和setter方法 //.... }
5.2 编写dao和service
5.2.1 编写dao接口
package com.czxy.dao; import com.czxy.domain.User; import tk.mybatis.mapper.common.Mapper; public interface UserMapper extends Mapper<User> { }
5.2.2 编写service接口
package com.czxy.service; import com.czxy.domain.User; public interface UserService { public User findByPrimaryKey(String uid); public void insertUser(User user); }
5.2.3 编写Service实现类
package com.czxy.service.impl; import com.czxy.dao.UserMapper; import com.czxy.domain.User; import com.czxy.service.UserService; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; @Service @Transactional public class UserServiceImpl implements UserService { @Resource private UserMapper userMapper; @Override public User findByPrimaryKey(String uid) { return userMapper.selectByPrimaryKey(uid); } @Override public void insertUser(User user) { userMapper.insert(user); } }
5.3 配置类
5.3.1 spring配置类
1.配置注解
1.1 扫描注解包
1.2加载properties文件
1.3 开启注解事务支持
2.获得properties数据(实现类、@Value)
3.配置数据源DataSource
4.配置事务管理器(DataSourceTransactionManager)
package com.czxy.config; import com.alibaba.druid.pool.DruidDataSource; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.PropertySource; import org.springframework.context.support.PropertySourcesPlaceholderConfigurer; import org.springframework.jdbc.datasource.DataSourceTransactionManager; import org.springframework.transaction.annotation.EnableTransactionManagement; import org.springframework.transaction.annotation.TransactionManagementConfigurer; import javax.annotation.Resource; import javax.sql.DataSource; @Configuration @ComponentScan(basePackages = {"com.czxy"}) @EnableTransactionManagement @PropertySource(value = "classpath:db.properties") public class SpringConfig { // 4.2.4版本 固定配置 @Bean public static PropertySourcesPlaceholderConfigurer create(){ return new PropertySourcesPlaceholderConfigurer(); } //读取数据库相关配置 @Value("${jdbc.driver}") private String driverClass; @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; //数据源使用德鲁伊连接池 @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); ds.setDriverClassName(driverClass); ds.setUrl(url); ds.setUsername(username); ds.setPassword(password); return ds; } // 开启事务管理器 @Bean @Resource public DataSourceTransactionManager txManager(DataSource dataSource){ return new DataSourceTransactionManager(dataSource); } }
5.3.2 mybatis配置类【新内容】
如下代码直接当做配置类拷贝即可.
1.配置session工厂,spring和MyBatis整合时,通过SqlSessionFactoryBean获得SqlSessionFactory
SqlSessionFactoryBean只能加载mapper映射文件
注解开发需要加载Mapper类,故需要对mapper进行扫描
2.配置映射扫描器
package com.czxy.config; import com.github.pagehelper.PageHelper; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.mybatis.spring.SqlSessionFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import tk.mybatis.spring.mapper.MapperScannerConfigurer; import javax.annotation.Resource; import javax.sql.DataSource; import java.util.Properties; /💃 * Spring整合MyBatis的配置类 * @author Administrator */ // 声明 @Configuration public class MyBatisConfig { /💃 * 构建SessionFactory对象,SessionFactory可以创建Session对象,最后使用Session操作数据库 * @param dataSource * @return * @throws Exception */ @Bean @Resource public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { // 1.通过工厂bean创建对象,最后需要调用 getObject()获得具体的对象 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean(); // 1.1 设置数据源 factoryBean.setDataSource(dataSource); // 1.2 设置别名包扫描 factoryBean.setTypeAliasesPackage("com.czxy.domain"); // 1.3 全局配置:驼峰映射 org.apache.ibatis.session.Configuration config = new org.apache.ibatis.session.Configuration(); config.setMapUnderscoreToCamelCase(true); factoryBean.setConfiguration(config); // 2 插件配置 // 2.1 分页插件 PageHelper pageHelper = new PageHelper(); Properties pageProps = new Properties(); pageProps.setProperty("dialect", "mysql"); pageProps.setProperty("rowBoundsWithCount", "true"); pageHelper.setProperties(pageProps); factoryBean.setPlugins(new Interceptor[] { pageHelper }); // 3 通过工厂bean获得 sqlSessionFactory return factoryBean.getObject(); } /💃 * 扫描Dao的包,查找各种XxxMapper接口,创建好UserMapper等对象存入到IOC的容器中 * @return */ @Bean public MapperScannerConfigurer mapperScanner() { MapperScannerConfigurer configurer = new MapperScannerConfigurer(); configurer.setBasePackage("com.czxy.dao"); return configurer; } }
5.4 测试类
5.4.1 方式1:整合Junit
package com.czxy.test; import com.czxy.config.MyBatisConfig; import com.czxy.config.SpringConfig; import com.czxy.domain.User; import com.czxy.service.UserService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import javax.annotation.Resource; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfig.class,MyBatisConfig.class}) public class TestA { @Resource private UserService userService; @Test public void test01(){ User user = new User("u001","张三丰","123"); userService.insertUser(user); System.out.println("添加完毕"); } }
测试结果: 成功添加用户信息
5.4.2 方式2:手动创建工厂
public class TestB { public static void main(String[] args) { //1.创建工厂,设置两个配置类 ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class,MyBatisConfig.class); //2.从工厂获取需要的对象 UserService userService = applicationContext.getBean(UserService.class); //3.执行语句 User user = new User("u800","zhangsan","123"); userService.insertUser(user); System.out.println("添加完毕"); } }
5.4.3 完整的UserService
userService: public interface UserService { void addUser(User user); void updateUser(User user); void delUser(String uid); User findByPrimaryKey(String uid); List<User> findAll(); List<User> findByPage(); } userServiceImpl: @Service @Transactional // 第五节讲解 public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; public void addUser(User user) { userMapper.insert(user); } public void updateUser(User user) { userMapper.updateByPrimaryKey(user); } public void delUser(String uid) { userMapper.deleteByPrimaryKey(uid); } public User findByPrimaryKey(String uid) { return userMapper.selectByPrimaryKey(uid); } public List<User> findAll() { return userMapper.selectAll(); } public List<User> findByPage() { // PageHelper // 第一个参数:第几页 // 第二个参数:获取条数 ,PageHelper这个代码后的第一个sql语句进行分页 PageHelper.startPage(1, 2); // 查找 // userMapper.selectAll(); List<User> list = userMapper.select(null); // 封装到pageInfo PageInfo<User> info = new PageInfo<User>(list); return info.getList(); } }
1 什么是Spring
2 什么是IOC
3 什么是AOP
在不改变原代码的情况下,对已有功能进行增强
目标对象
连接点
切入点
通知/增强(前置通知、后置通知、环绕通知…)
切面
代理
4 常用注解
IOC:
@Component
@Repository
@Service
@Controller
@Resource
@Autowired
@Bean
@RunWith
@ContextConfiguration
@Configuration
AOP:@After @Before @AfterReturning @AfterThrowing @Around @Joinpoint
6. 事务管理
6.1 案例:转账
6.1.1 需求描述:
完成转账功能, 根据两个账户的id和要转账的钱money,对其中一个id的钱-money,对另一个id的钱+money.
分析:
转账功能 需要由两个动作完成.
- 账户1减钱
- 账户2加钱
减钱:根据账户id对钱数 减
加钱:根据账户id 对钱数 加
总体思路:
\1. SM整合环境搭建好.
\2. 在service中提供转账功能.
\3. 测试类中测试.转账功能
项目结构如下:
6.1.2 环境搭建
l 步骤1:导入jar包坐标
l 步骤2:配置文件
l 步骤3:数据库和表
CREATE TABLE account( id INT PRIMARY KEY AUTO_INCREMENT, NAME VARCHAR(40), money FLOAT ); INSERT INTO account(NAME,money) VALUES('jack',10); INSERT INTO account(NAME,money) VALUES('rose',10);
6.1.3 编写domain
public class Account { @Id private Integer id; private String name; private Double money; //省略getter和setter方法 }
6.1.4 编写dao
public interface AccountMapper extends Mapper<Account> { }
6.1.5 编写service
AccountService接口:
public interface AccountService { public void change(Integer inId,Integer outId,Double money); }
AccountServiceImpl实现类:
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; public void change(Integer inId, Integer outId, Double money) { //加钱 Account account = accountMapper.selectByPrimaryKey(inId); account.setMoney(account.getMoney()+money); accountMapper.updateByPrimaryKey(account); //减钱 Account account1 = accountMapper.selectByPrimaryKey(outId); account1.setMoney(account1.getMoney()-money); accountMapper.updateByPrimaryKey(account1); } }
6.1.6 测试
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = {SpringConfig.class,MyBatisConfig.class}) public class TestA { @Resource private AccountService accountService; @Test public void test01(){ accountService.change(1,2,1d); System.out.println("完毕"); } }
测试结果: 执行成功结果正确
修改Service中的代码,继续测试:
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; public void change(Integer inId, Integer outId, Double money) { Account account = accountMapper.selectByPrimaryKey(inId); account.setMoney(account.getMoney()+money); accountMapper.updateByPrimaryKey(account); int i=1/0; Account account1 = accountMapper.selectByPrimaryKey(outId); account1.setMoney(account1.getMoney()-money); accountMapper.updateByPrimaryKey(account1); } }
结果: 一个账号加钱了, 一个账号没减钱
l 事务的概述
n 事务指的是逻辑上的一组操作,组成这组操作的各个单元要么全都成功,要么全都失败.
n 事务作用:保证一组操作要么全都成功,对数据库进行完整更新。要么在某一个动作失败的时候让数据恢复原状,不会引起不完整的修改。
6.2 事务概述
JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计 业务层 的事务处理解决方案。
spring的事务控制是基于AOP的。也就是spring提供了实现类对我们的业务方法进行增强,完成事务具体的操作。
6.3 Spring事务相关的术语
6.3.1 事务平台管理器: PlatformTransactionManager
spring通过事务管理器来管理事务。
事务管理器PlatformTransactionManager提供了事务需要的基本操作。
实现类 | 描述 |
DataSourceTransactionManager | 使用JdbcTemplate或MyBatis需要的事务管理器 |
6.4 事务入门
6.4.1 修改配置类
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; public void change(Integer inId, Integer outId, Double money) { Account account = accountMapper.selectByPrimaryKey(inId); account.setMoney(account.getMoney()+money); accountMapper.updateByPrimaryKey(account); int i=1/0; Account account1 = accountMapper.selectByPrimaryKey(outId); account1.setMoney(account1.getMoney()-money); accountMapper.updateByPrimaryKey(account1); } }
6.4.2 修改Service
@Service public class AccountServiceImpl implements AccountService { @Autowired private AccountMapper accountMapper; public void change(Integer inId, Integer outId, Double money) { Account account = accountMapper.selectByPrimaryKey(inId); account.setMoney(account.getMoney()+money); accountMapper.updateByPrimaryKey(account); int i=1/0; Account account1 = accountMapper.selectByPrimaryKey(outId); account1.setMoney(account1.getMoney()-money); accountMapper.updateByPrimaryKey(account1); } }
6.5 事务高级 ( 面试必背 )
6.5.1 事务特性:ACID
l 原子性(Atomicity)原子性是指事务是一个不可分割的工作单位,事务中的操作要么都成功,要么都失败。
l 一致性(Consistency)事务前后数据的完整性必须保持一致。
l 隔离性(Isolation)事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务干扰,多个并发事务之间数据要相互隔离。
l 持久性(Durability)持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
6.5.2 并发访问问题
如果不考虑隔离性,事务存在并发访问问题。
脏读:读未提交
不可重复度:读已提交(update)两次读取的结果不一致
幻读/虚读:读已提交(insert)两次读取的结果不一致
\1. 脏读:(读未提交)一个事务读到了另一个事务未提交的数据.
\2. 不可重复读(读已提交):一个事务读到了另一个事务已经提交(update)的数据。引发另一个事务,在事务中的多次查询结果不一致。(要保证同一个事物的多次查询同一个数据,结果一致)
\3. 虚读 /幻读:(读已提交)一个事务读到了另一个事务已经提交(insert)的数据。导致另一个事务,在事务中多次查询的结果不一致。(数据量不同)
严重性: 脏读 > 不可重复读 >虚读(幻读)
6.5.3 隔离级别:解决问题
l 数据库规范规定了4种隔离级别,分别用于描述两个事务并发的所有情况。
\1. read uncommitted 读未提交,一个事务读到另一个事务没有提交的数据。
a) 存在:3个问题(脏读、不可重复读、虚读)。
b) 解决:0个问题
效率最高,引发所有读问题
基本不用
\2. read committed 读已提交,一个事务读到另一个事务已经提交的数据。
a) 存在:2个问题(不可重复读、虚读)。
b) 解决:1个问题(脏读)
如果要 效率,那么选择这个read committed
\3. repeatable read :可重复读,在一个事务中读到的数据信息始终保持一致,无论另一个事务是否提交。
a) 存在:1个问题(虚读)。
b) 解决:2个问题(脏读、不可重复读)
如果 要求安全,选择这个repeatable read
虚读的问题可以通过程序来规避:
- 事务刚开启时,可以count(*)
- 事务要关闭时,可以count(*)
- 比对,如果两次数据一致,说明没有虚读
\4. serializable 串行化,同时只能执行一个事务,相当于事务中的单线程。
a) 存在:0个问题。
b) 解决:1个问题(脏读、不可重复读、虚读)
效率最低,安全性最高,基本不用
l 安全和性能对比
n 安全性:serializable > repeatable read > read committed > read uncommitted
n 性能 : serializable < repeatable read < read committed < read uncommitted
l 常见数据库的默认隔离级别:
n MySql:repeatable read 安全,本身做的优化比较好
n Oracle:read committed 效率
6.5.4 术语
事务:
TransactionDefinition:事务的定义信息对象,需要知道,方便知道事务有哪些设置项。
TransactionStatus:事务的状态对象,spring内部使用的对象,不需要关注。
6.5.5 定义对象:概述 TransactionDefinition
通过分析事务定义信息对象,spring事务定义主要涉及以下4方面:隔离级别、传播行为、超时、只读
6.5.6 定义对象:只读
开发中,查询数据,不会伴随增删改,所以建议查询时设置为只读。
注解 | 描述 |
@Transactional(readOnly=true) | 只读事务。DQL使用 |
@Transactional(readOnly=false) | 默认值,不是只读事务,可以进行增删改操作。DML使用 |
6.5.7 定义对象:超时
默认值是-1,没有超时限制。如果有,以秒为单位进行设置。(一般不设置)
注解 | 描述 |
@Transactional(timeout=60) | 设置超时为60秒,如果还没有操作结束,将抛异常。 |
6.5.8 定义对象:隔离级别
事务隔离级别反映事务提交并发访问时的处理态度。
注解 | 描述 |
@Transactional(isolation=Isolation.DEFAULT) | 默认级别 |
@Transactional(isolation=Isolation.READ_UNCOMMITTED) | 读未提交 |
@Transactional(isolation=Isolation.READ_COMMITTED) | 读已提交 oracle |
@Transactional(isolation=Isolation.REPEATABLE_READ) | 可重复读 mysql |
@Transactional(isolation=Isolation.SERIALIZABLE) | 串行化 |
6.5.9 定义对象:传播行为 ( Spring特有 )
传播行为:业务A使用了业务B,AB之间事务共享问题,就是事务的传播行为。
spring中事务的传播行为共7种。
注解 | 描述 |
@Transactional(propagation=Propagation.REQUIRED) | 默认值,支持当前事务,如果当前没有事务,就新建一个事务 |
@Transactional(propagation=Propagation.SUPPORTS) | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
@Transactional(propagation=Propagation.MANDATORY) | 支持当前事务,如果当前没有事务,就抛出异常 |
@Transactional(propagation=Propagation.REQUIRES_NEW) | 新建事务,如果当前存在事务,把当前事务挂起 |
@Transactional(propagation=Propagation.NOT_SUPPORTED) | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起 |
@Transactional(propagation=Propagation.NEVER) | 以非事务方式执行,如果当前存在事务,则抛出异常 |
@Transactional(propagation=Propagation.NESTED) | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行REQUIRED类似的操作 |
7. 附录
Pom01-Spring入门
<properties> <spring.version>4.2.4.RELEASE</spring.version> </properties> <!-- spring start --> <!--spring core start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!--spring core end--> <!--spring aop start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <!--spirng aop end--> <!--spring aspects start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <!--spring aspects end--> <!--spring instrumentation start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>${spring.version}</version> </dependency> <!--spring instrumentation end--> <!--spring messaging start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency> <!--spring messaging end--> <!--spring data access start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <!--spring data access end--> <!--spring web start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc-portlet</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!--spring web end --> <!--spring test start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!--spring test end --> <!-- spring end --> rsion>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>${spring.version}</version> </dependency> <!--spring core end--> <!--spring aop start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>${spring.version}</version> </dependency> <!--spirng aop end--> <!--spring aspects start--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <!--spring aspects end--> <!--spring instrumentation start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-instrument</artifactId> <version>${spring.version}</version> </dependency> <!--spring instrumentation end--> <!--spring messaging start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>${spring.version}</version> </dependency> <!--spring messaging end--> <!--spring data access start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-oxm</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jms</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>${spring.version}</version> </dependency> <!--spring data access end--> <!--spring web start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-web</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc-portlet</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!--spring web end --> <!--spring test start --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>${spring.version}</version> </dependency> <!--spring test end --> <!-- spring end -->