1.什么是spring框架
1.spring是一款开源框架,解决企业开发的复杂性。
2.spring框架提供了三大核心思想:IOC、AOP、DI
IOC:控制反转。创建对象并管理生命周期。
AOP:面向切面编程。不改变源码对代码进行扩展。
DI:依赖注入。
3.spring框架特点:
1.方便解耦,简化开发。
2.AOP编程的支持--更方便对源码进行扩展
3.声明式事务的支持
4.方便集成各种优秀框架--mybatis hibernate mybtais-plus等
5.方便程序的测试--spring提供了自己的单元测试
1.2为什么使用spring框架
初级
工厂模式
工厂模式虽然解决了等号右边创建对象的依赖的问题,但是没有解决等于左边的依赖问题。可以使用接口。如果这时1000个依赖Food的类,Food即使升级为Food1也只需要改变FoodFactory中这一处代码即可。我们自己通过工厂模式+接口虽然解决了上面类与类之间的依赖关系。但是还有很多考虑不周的地方,那么是否存在一款框架能更好的解决上面的依赖关系吗?--有。spring提供的IOC技术
2.spring-IOC
控制反转(Inversion of Control,缩写IOC),是面向对象编程中的一种设计原则,可以用来降低计算机代码之间的耦合度。
--先注入依赖--把类交与spring容器进行管理--测试类中加载spring文件利用getBean()随时可调用类的内容
其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,交于spring容器来帮助创建对象。
1.依赖--还是和springMVC一样的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
2.创建spring配置文件
<!--交于spring创建bean对象
id:bean的唯一标识
class:表示哪个类交于spring创建
-->
<bean id="f" class="com.wjy.demo.Food"></bean>
</beans>
3.测试
public class Test01 {
public static void main(String[] args) {
//1.加载spring的配置文件
ApplicationContext app = new ClassPathXmlApplicationContext("classpath:spring.xml");
//2.从spring容器中获取指定的对象
//获取spring容器中bean的方式
//方式1,需要强制转化
/* IFood f =(IFood) app.getBean("f");
f.show();
//方式2,如果容器中存放多个匹配的对象时,则出现错误(适用于一个时)
IFood bean = app.getBean(IFood.class);
bean.show();
//方式3,结合上面两种(推荐使用)
IFood bean1 = app.getBean("f2", IFood.class);
bean1.show();*/
/* ApplicationContext app = new ClassPathXmlApplicationContext("classpath:spring02.xml");
IFood food = (IFood) app.getBean("food");
food.show();*/
}
}
2.1获取spring的bean的方式
获取spring容器中bean的方式
①通过id获取--需要强制转化
IFood f =(IFood) app.getBean("f");
f.show();
②通过反射类--适用于一个时
IFood bean = app.getBean(IFood.class);
bean.show();
③结合上面两种(推荐)
IFood bean1 = app.getBean("f2", IFood.class);
bean1.show();
2.2使用spring注解完成bean的创建
上面我们通过spring的xml文件来创建和管理bean对象,如果我们的bean有100个,我们就需要在xml文件中创建100bean标签。这样非常麻烦。 我们可以使用注解@完成spring的bean创建。
1.修改spring配置文件
<!--指定扫描的包-->
<context:component-scan base-package="com.wjy.demo"/>
</beans>
在类上使用注解
@Component //表示该类交于spring容器创建。前提:该类能被spring扫描到 value表示为该bean起名称。如果没有value属性 默认为类的首字母小写
public class Food implements IFood {
public void show() {
System.out.println("food1~~~~~~~~~~~~~~~~~~~~~~😀");
}
}
测试
public class Test {
public static void main(String[] args) {
//加载spring配置文件
ApplicationContext app=new ClassPathXmlApplicationContext("classpath:spring02.xml");
IFood f = (IFood) app.getBean("food");//第一种通过id获取bean
f.show();
}
}
2.3常见的注解:
@Component:加在类上,表示该类交于spring容器来管理。
--是下面三种的底层,作用都是一样的。(为了区分不同的层)
@Controller:用于控制层
@Servlet:用于业务层
@Repository:用于持久层dao
2.4spring的bean的作用域
spring支持的bean的作用域:默认为singleton
singleton:单例模式无论获取多少个bean对象,spring容器始终只创建一次。
prototype:多例模式,每次获取bean对象时,spring都会new一个新的该类对象
request:每次请求都会创建一个新的对象--web开发
session:同一个会话共享一个bean对象--web开发
方式一:
方式二:
3.spring的依赖注入DI
DI(Dependency Injection):依赖注入,就是给对象中属性注入相应的值。
IOC控制反转的最终目的就是降低程序的耦合,也就是削减依赖关系。
依赖关系的管理以后交给spring维护,依赖关系的管理就称之为依赖注入。也就是说当前类需要用到其他类的对象时,由spring提供,只需要在配置文件中说明依赖关系的维护就可以了。
3.1注入方式
spring有三种注入属性的方式:
1.通过set方法注入:<property name="属性名" ref="引入其他bean">
2.通过构造方法注入:<constructor-arg name="属性名" ref="引入其他bean">
3.自动注入<bean autowire="byName/byType" />底层使用的还是set方法
byName:通过属性名注入 按照属性名在容器中找对应名称的bean
byType:通过属性的类型注入 按照属性的类型在容器中找匹配的类型的bean
1.set注入:
2.构造注入
3.自动注入
①通过名字
②通过样式
测试
3.2spring注入的属性类型
<bean class="com.wjy.demo03.Student">
<!--基本类型和字符串类型注入的属性值用value-->
<property name="age" value="15"/>
<property name="name" value="gyh"/>
<!--ref:引入其他的bean-->
<property name="date" ref="d"/>
<!--集合List-->
<property name="list">
<list>
<value>游泳</value>
<value>唱歌</value>
<value>跳舞</value>
</list>
</property>
<!--集合Set-->
<property name="set">
<set>
<value>中国</value>
<value>中国</value>
<value>日本</value>
<value>美国</value>
</set>
</property>
<!--map-->
<property name="map">
<map>
<entry key="name" value="王佳瑶"/>
<entry key="hobby" value="追剧"/>
</map>
</property>
</bean>
<bean id="d" class="java.util.Date"></bean>
ApplicationContext app = new ClassPathXmlApplicationContext("spring03.xml");
Student student = app.getBean(Student.class);
List<String> list = student.getList();
for (String l :
list) {
System.out.println(l);
}
Set<String> set = student.getSet();
for (String s :
set) {
System.out.println(s);
}
Map<String, Object> map = student.getMap();
System.out.println(map);
4.AOP面向切面
AOP(Aspect Oriented Programming)面向切面思想,一般称为面向切面,作为面向对象OOP的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块【类】,这个模块被命名为“切面”(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性。可用于权限认证、日志、事务处理。
Java是一个面向对象(OOP)的编程语言,但它有个弊端就是当需要为多个不具有继承关系的对象引入一个公共行为时,例如日志记录、权限校验、事务管理、统计等功能,只能在每个对象里都引用公共行为,这样做不便于维护,而且有大量重复代码,AOP的出现弥补了OOP的这点不足。
有多少个业务操作,就要写多少重复的校验和日志记录代码,这显然是无法接受的。当然用面向对象的思想,可以把这些重复的代码抽离出来,写成公共方法,就是下面这样
代码冗余和可维护性的问题得到了解决,但每个业务方法中依然要依次手动调用这些公共方法,也是略显繁琐。 有没有更好的方式呢?有的,那就是AOP,AOP将权限校验、日志记录等非业务代码完全提取出来,与业务代码分离,并寻找节点切入业务代码中 。
业务代码和非业务代码完全分离。
动态代理:基于实现接口的jdk动态代理、Cglib动态代理
4.1AOP体系结构
AOP要做的三件事:
1.在哪里切入,也就是权限校验,等非业务操作在哪些业务代码中执行;
2.什么时候切入,是业务代码执行前还是执行后;
3.切入后做什么事,比如做权限校验、日志记录等。
- Pointcut:切点,决定处理如权限校验、日志记录等在何处切入业务代码中(即织入切面)。切点分为execution方式和annotation方式。前者可以用路径表达式指定哪些类织入切面,后者可以指定被哪些注解修饰的代码织入切面。
- Advice:处理,包括处理时机和处理内容。处理内容就是要做什么事,比如校验权限和记录日志。处理时机就是在什么时机执行处理内容,分为前置处理(即业务代码执行前)、后置处理(业务代码执行后)等。
- Aspect:切面,即Pointcut和Advice。
- Jointpoint:连接点,是程序执行的一个点。例如,一个方法的执行或者一个异常的处理。在 Spring AOP 中,一个连接点总是代表一个方法执行。
- Weaving:织入,就是通过动态代理,在目标对象方法中执行处理内容的过程。
@Aspect// 表示这是一个切面类
@Component// 表示该类对象交于spring容器来管理
public class MyAspect {
//1.使用execution()来使用路径模式--(不灵活、不会改变源码)
// 定义一个切入点
/* @Pointcut("execution(public int com.wjy.aop.SumServiceImpl.add(int,int))"+
"||execution(public int com.wjy.aop.SumServiceImpl.sub(int,int))"+
"||execution(public int com.wjy.aop.SumServiceImpl.mul(int,int))"+
"||execution(public int com.wjy.aop.SumServiceImpl.div(int,int))")*/
//上面可以针对每个方法进行切入--但是麻烦(可以使用通配符)
// @Pointcut("execution(public int com.wjy.aop.SumServiceImpl.*(int,int))")
// @Pointcut("execution(* com.wjy.aop.SumServiceImpl.*(int,int,int))")
//..任意参
//* com.wjy.aop.*.*(..)
// 任意返回值类型和修饰符,该包下的任意类下任意方法的任意参数
//第一个*表示任意返回值类型
//第二个*表示任意类
//第三个*表示任意方法
//..表示任意参数
/* @Pointcut("execution(* com.wjy.aop.*.*(..))")
private void myPointCut() {}*/
//2.使用注解模式--(灵活、需要在源码的地方修改注解)
//凡是使用org.springframework.web.bind.annotation.GetMapping注解的地方都会执行切点
//或者org.springframework.web.bind.annotation.RequestMapping注解的地方
/* @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping)||@annotation(org.springframework.web.bind.annotation.RequestMapping)")
private void myPointCut() {};*/
//定义处理时机--通知类型--在上面方法执行前执行
/* @Before("myPointCut()")
public void myBefore(){
System.out.println("这是一个前置处理");
}*/
//3.自定义注解--spring框架自带的注解
@Pointcut("@annotation(com.wjy.annotation.MyAnnotation)")
private void myPointCut() {};
//定义处理时机--通知类型--在上面方法执行前执行
/* @Before("myPointCut()")
public void myBefore(JoinPoint joinPoint){
/*//后置处理
@After("myPointCut()")
public void myAfter(JoinPoint joinPoint){
System.out.println("myAfter后置处理");
}
//异常处理
@AfterThrowing("myPointCut()")
public void myAfterThrowing(JoinPoint joinPoint){
System.out.println("myAfterThrowing异常处理");
}
//后置返回通知
@AfterReturning("myPointCut()")
public void myAfterReturning(JoinPoint joinPoint){
System.out.println("myAfterReturning后置返回通知");
}*/
//环绕处理
@Around("myPointCut()")
public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
------------------------
try{
//调用切入点的方法
result = joinPoint.proceed();
System.out.println("业务执行完毕后需要执行的代码");
}catch (Exception e){
System.out.println("出现异常后执行的代码");
}finally {
System.out.println("有无异常都会执行的代码");
}
return result;
}
package com.wjy.aop;
public interface SumService {
//加方法
public int add(int a, int b);
//减方法
public int sub(int a, int b);
//乘方法
public int mul(int a, int b);
//除方法
public int div(int a, int b);
//
public Double sum(int a,int b,int c);
}
@Service
public class SumServiceImpl implements SumService {
@GetMapping("")
public int add(int a, int b) {
return a+b;
}
// @MyAnnotation(value = "自定义注解没有默认值时,这里要定义")
public int sub(int a, int b) {
return a-b;
}
@RequestMapping("")
public int mul(int a, int b) {
return a*b;
}
//自定义注解中的属性,同时给多个赋值,要写全
@MyAnnotation(value = "nihao",age = 18)
public int div(int a, int b) {
return a/b;
}
@Override
@GetMapping("")
public Double sum(int a, int b, int c) {
double su=a+b-c;
return su;
}
}
public class Test01 {
public static void main(String[] args) {
//从容器中获取对象
ApplicationContext app = new ClassPathXmlApplicationContext("spring.xml");
SumService bean = app.getBean(SumService.class);
double add = bean.div(4, 2);
System.out.println(add);
@Target({ElementType.METHOD,ElementType.TYPE})// 注解作用范围: 方法、类
@Retention(RetentionPolicy.RUNTIME)// 注解保留时间: 运行时
public @interface MyAnnotation {
//定义注解中的属性
//格式:数据类型 属性名() default 默认值;
//String value() default "默认值";
//没有默认值,使用该注解的地方要定义value="值";
// String value();
String value() default "hello";
int age() default 0;
}
//spring.xml
<!--包扫描-->
<context:component-scan base-package="com.wjy.aop"></context:component-scan>
<!--开启切面功能 默认无法识别Aspect注解-->
<mvc:aspectj-autoproxy/>
//pom.xml
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.15.RELEASE</version>
</dependency>
4.2AOP原理(重点)
底层实现原理: 动态代理。
实现动态代理的方式有两种:
JDK动态代理(代理的类实现了接口)和Cglib动态代理(没有实现接口)。
提到JDK代理和Cglib代理两种动态代理,优秀的Spring框架把两种方式在底层都集成了进去,无需担心自己去实现动态生成代理。
那么Spring是如何生成代理对象的?
创建容器对象的时候,根据切入点表达式拦截的类,生成代理对象。
如果目标对象有实现接口,使用jdk代理。如果目标对象没有实现接口,则使用Cglib代理。
然后从容器获取代理后的对象,在运行期植入"切面"类的方法。
如果目标类没有实现接口,且class为final修饰的,则不能进行Spring AOP编程。
5.事务
5.1什么是事务?
官方:一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全体操作组成。
这些组成要么都执行要么都不执行。
解释:把数据库的各种操作封装到一个事务中,这些操作数据库的动作要么都执行要么都不执行。
(就是把多个要做的操作组合成一个整体,利用事务的特性来保证操作的安全性。---如果一个事务做到一半出现任何错误,就会进行回滚操作,来恢复成最初的模样。)
5.2事务的特点?--ACID
原子性(atomicity):事务是一个原子操作,有一系列动作组成,事务的原子性确保动作要么全部完成要么完全不起作用。
一致性(consistency):一致性是指事务必须使数据库从一个一致性状态变换到另一个一致性状态,也就是说一个事务执行之前和执行之后都必须处于一致性状态。
---举例来说,假设用户A和用户B两者的钱加起来一共是1000,那么不管A和B之间如何转账、转几次账,事务结束后两个用户的钱相加起来应该还得是1000,这就是事务的一致性。
隔离性(isolation):可能有许多事务会同时处理相同的数据,因此每个事务都该与其他事务隔离不开,防止数据损坏。
持久性(durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,通常情况下,事务的结果被写道持久化存储器中。
5.3事务并发带来的问题
总结:
脏读:读取了其他事务未提交的数据。
不可重复读:两次读取的内容不一样。
幻读:两次读取的数量不一样。
5.3.1脏读
脏读也俗称“读未提交”--就是某一事务A读取到了事务B未提交的数据。
流程图解释:
事务A先查询张三年龄,随后事务B修改张三的年龄,事务A读取张三的年龄,然后提交事务A,事务B回滚。
那事务A第二次查出的数据就是错的,所以就是脏读。
5.3.2不可重复读
不可重复读:在一个事务内,多次读取同一个数据,却返回了不同的结果。
实际上,这是因为在该事务间隔读取数据的期间,有其他事务对这段数据进行了修改,并且已经提交,就会发生不可重复读事故。
流程图解释:事务A先查询张三年龄,随后事务B修改张三年龄,直接提交了事务B,然后事务A又读取张三年龄,读取两个不一样的数据。
5.3.3幻读
幻读:幻读是指当事务不独立执行时,插入或者删除另一个事务当前影响的数据而发生的一种类似幻觉的现象。
流程图解释:事务A先查询C表总数,随后事务B删除一条记录,直接提交了,然后事务A又查询C表总数,两次总数不一样。
5.4如何解决事务并发带来的问题
事务数据库的隔离级别来解决事务并发带来的问题,不同的隔离级别可以解决不同的问题。
隔离级别越高---性能越弱。
① read uncommited(读取未提交内容) :在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。读取未提交的数据,也被称之为脏读(Dirty Read):脏读幻读 不可重复都能发生
② read committed(读取提交内容):这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。可解决脏读,无法解决不可重复和幻读。
③ repeatable read(可重读):这是MySQL的默认事务隔离级别,同一事务的多个实例在并发读取数据时,会看到同样的数据。不过理论上,这会导致另一个棘手的问题:幻读(Phantom Read)。可解决脏读、不可重复读。
④ serializable (可串行化) :这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。可解决脏读、不可重复读、幻读。
msql8.0以后使用select @@GLOBAL.transaction_isolation;查看全局隔离级别。
select @@transaction_isolation; 当前会话的隔离级别。
企业中一般不改。
5.5jdbc如何实现事务
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class Test01 {
public static void main(String[] args) {
Connection connection= null;
try {
Class.forName("com.mysql.cj.jdbc.Driver");
connection= DriverManager.getConnection("jdbc:mysql://localhost:3306/qy174-mrcai?serverTimezone=Asia/Shanghai","root","root");
//开启事务手动提交
connection.setAutoCommit(false);
String sql="update tbl_user set balance=balance-1000 where userid=1";
PreparedStatement preparedStatement1 = connection.prepareStatement(sql);
preparedStatement1.executeUpdate();
int c=10/0; //jdbc默认事务自动。每执行一个sql事务就会自动提交。
String sql2="update tbl_user set balance=balance+1000 where userid=2";
PreparedStatement preparedStatement2 = connection.prepareStatement(sql2);
preparedStatement2.executeUpdate();
//手动提交事务--
connection.commit();
}catch (Exception e){
e.printStackTrace();
//事务回滚
try {
connection.rollback();
} catch (SQLException ex) {
throw new RuntimeException(ex);
}
}finally {
}
}
}
事务的开启---事务的提交---事务的回滚---资源的关闭。 如果写事务代码每次都需要写这些重复的代码。--我们学习过aop。spring框架帮你封装了一个事务切面类。
5.6spring如何实现事务
1.事务jar包
<!--spring事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
2.修改配置文件
<!--注入事务切面类 必须为transactionManager-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
</bean>
<!--开启事务注解驱动-->
<tx:annotation-driven/>