Spring知识点总结

目录

1、Spring概念介绍

什么是Spring

2、IOC与DI概念

什么是责任链

基于责任链模式开发的缺点

什么是IOC

什么是DI

3、IOC实现原理

IOC实现原理

BeanFactory与ApplicationContext接口

使用Spring

4、XML实现DI

DI给对象属性赋值

什么是工厂模式

一些Bean标签

5、bean作用域、生命周期与自动装配

bean作用域

bean的生命周期

通知处理器的使用

bean的自动装配

6、引入外部属性配置文件

7、注解方式管理bean

注解方式实现IOC

注解方式实现DI

配置类方式实现IOC和DI

8、代理模式

什么是代理模式

代理模式的作用

静态代理

动态代理

JDK动态代理

cglib动态代理

9、AOP的概念和原理

什么是AOP

AOP实现原理

AOP的一些术语

AOP五个注解

10、AOP注解方式的实现

注解方式实现AOP

JoinPoint对象

ProceedingJoinPoint对象

通过配置类

11、事务

事务的概念

事务的四大特性

事务的并发问题

事物的隔离级别

12、日志和测试

日志的级别

使用日志框架

junit5单元测试


1、Spring概念介绍

什么是Spring

Spring是开源的、轻量级的、企业级的应用集合框架,是包含众多工具方法的IOC容器

2、IOC与DI概念

什么是责任链

使用MVC开发的时候数据在各层之间传递,在业务上形成一个链条

基于责任链模式开发的缺点

当最底层的类被修改时,那么整个调用链的所有类都需要被修改。造成耦合度比较高。

什么是IOC

IOC就是控制反转,处理对象的创建

控制:对类实例化的控制,也就是创建对象这件事

反转:原本由程序员创建实例,现在由Spring进行实例化

什么是DI

DI就是依赖注入,处理属性的赋值

依赖:一个类在另一个类中作为全局属性时

注入:通过Spring容器为自己的属性注册一个值

3、IOC实现原理

IOC实现原理

1.XML解析技术读取配置文件

<bean id="u1" class="com.mdh.demo.User"></bean>

将上面的信息读入程序,一个是对象的id,一个是对象的类的全路径名

2.反射技术实例化对象,放到容器中

(1)首先获得类的字节码

Class clazz = Class.forName("com.mdh.demo.User");

(2)然后通过字节码实例化对象

Object obj = clazz.newInstance();

(3)最后将对象放到一个类似map的集合中

map.put("u1",obj);

3.工厂模式返回Bean对象

public Object getBean(String name){
    Object obj = map.get(name);
    return obj;
}

BeanFactory与ApplicationContext接口

BeanFactroy:Bean工厂

ApplicationContext:应用上下文

1.ApplicationContext是BeanFactroy的子类,提供了更多面向实际应用的功能,例如:对国际化的支持,支持资源的访问,支持时间的传播

2.BeanFactory采用延迟加载Bean,在第一次使用getBean方法获取Bean实例时才加载Bean;而ApplicationContext在自身被实例化时一次完成所有Bean创建

3.ApplicationContext类体系结构中,主要的实现类有两个

(1)ClassPathXmlApplicationContext从类路径加载配置文件

(2)FileSystemXmlApplicationContext从文件系统中加载配置文件(如c盘)

使用Spring

1.创建Maven项目,导入依赖包

 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-context</artifactId>
     <version>5.2.3.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>org.springframework</groupId>
     <artifactId>spring-beans</artifactId>
     <version>5.2.3.RELEASE</version>
 </dependency>

2.创建业务对象

3.在resource下配置xml

4.创建启动类或测试类进行使用

public class App {
    public static void main(String[] args) {
        //1.1获取到Spring的上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //1.2获取到Spring的上下文对象
        //BeanFactory beanFactory = new ClassPathXmlApplicationContext("applicationContext.xml");
        //BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("applicationContext.xml"));
​
        //2.1从Spring(上下文)中获取到业务对象,通过bean的id
        //UserBean user = (UserBean) context.getBean("user");
        //2.2从Spring(上下文)中获取到业务对象,通过对象类型
        //UserBean user = context.getBean(UserBean.class);
        //2.3从Spring(上下文)中获取到业务对象,通过id+对象类型
        User user = context.getBean("user1",User.class);
​
        //3.使用业务对象
        System.out.println(user);
    }
}

4、XML实现DI

DI给对象属性赋值

首先我们创建一个User类,并通过lombok添加构造方法和set、get方法

@Data
public class User {
    private int uid;
    private String username;
    private String password;
}

1.通过set方法

<bean id="u1" class="com.mdh.demo.User">
    <property name="uid" value="1"></property>
    <property name="username" value="张三"></property>
    <property name="password" value="123456"></property>
</bean>

先通过无参构造实例化,再用set方法赋值

2.通过有参构造

<bean id="u2" class="com.mdh.demo.User">
    <!-- 此处的参数:index表示形参下标,name表示形参名字,value表示基本类型的值,ref表示自定义的引用类型的值-->
    <constructor-arg index="0" value="2"></constructor-arg>
    <constructor-arg index="1" value="李四"></constructor-arg>
    <constructor-arg index="2" value="123456"></constructor-arg>
</bean>

通过有参构造实例化

3.通过p名称空间和c名称空间

<beans xmlns:p="http://www.springframework.org/schema/p" xmlns:c="http://www.springframework.org/schema/c">
    <bean id="u3" class="com.mdh.demo.User" p:uid="3" p:username="王五" p:password="123456"></bean>
    <bean id="u4" class="com.mdh.demo.User" c:uid="4" c:username="王六" c:password="123456"></bean>
</beans>

实际上是前两种的简化写法,注意要在beans里加约束

4.注入空值和特殊符号

<bean id="u1" class="com.mdh.demo.User">
    <!-- 空值 -->
    <property name="uid">
	    <null></null>
    </property>
    <!-- 特殊符号使用转义字符 <为&lt; >为&gt; &为&amp;-->
    <property name="username" value="&amp"></property>
    <!-- CDATA原样设置 -->
    <property name="password">
	    <value>
		    <![CDATA[内容]]>
        </value>
    </property>
</bean>

5.bean引用另一个bean

创建一个Student类,其属性中有Score类,并通过lombok添加构造方法和set、get方法

@Data
public class Score{
    private int java;
    private int cpp;
}
@Data
public class Student {
    private String name;
    private Score score;
}

(1)引用外部bean

<bean id="score1" class="com.mdh.demo.Score">
    <property name="java" value="100"></property>
    <property name="cpp" value="100"></property>
</bean>

<bean id="stu1" class="com.mdh.demo.Student">
    <property name="name" value="张三"></property>
    <property name="score" ref="score1"></property>
</bean>

(2)引用内部bean

<bean id="stu2" class="com.mdh.demo.Student">
    <property name="name" value="李四"></property>
    <property name="score">
        <bean class="com.mdh.demo.Score">
            <property name="java" value="100"></property>
            <property name="cpp" value="100"></property>
        </bean>
    </property>
</bean>

(3)级联引入bean

<bean id="score3" class="com.mdh.demo.Score"></bean>
<bean id="stu3" class="com.mdh.demo.Student">
    <property name="name" value="王五"></property>
    <property name="score" ref="score3"></property>
    <property name="score.java" value="100"></property>
    <property name="score.cpp" value="100"></property>
</bean>

原理为用反射调用get方法,获取对象之后,再赋值

6.注入集合

例如有这样一个类

@Data
public class Students {
    private String[] books;
    private List<String> bookList;
    private Set<String> bookSet;
    private Map<String,String> bookMap;
    private List<Book> bookList2;
}
public class Book {
    private String name;
    private String author;
}

配置xml

<bean id="students1" class="com.mdh.demo.Students">
    <!-- array数组注入 -->
    <property name="books">
        <array>
            <value>java</value>
            <value>cpp</value>
        </array>
    </property>
    <!-- list集合注入 -->
    <property name="bookList">
        <list>
            <value>java</value>
            <value>cpp</value>
        </list>
    </property>
    <!-- set集合注入 -->
    <property name="bookSet">
        <set>
            <value>java</value>
            <value>cpp</value>
        </set>

    </property>
    <!-- map集合注入 -->
    <property name="bookMap">
        <map>
            <entry key="java" value="bite"></entry>
            <entry key="cpp" value="bite"></entry>
        </map>
    </property>
    <!-- list对象集合注入 -->
    <property name="bookList2">
        <list>
            <ref bean="b1"></ref>
            <ref bean="b2"></ref>
        </list>
    </property>
</bean>
<!-- 声明多个book对象 -->
<bean id="b1" class="com.mdh.demo.Book" p:name="java" p:author="bite" ></bean>
<bean id="b2" class="com.mdh.demo.Book" p:name="cpp" p:author="bite" ></bean>

7.工厂方式获取bean

<bean id="bookFactory" class="com.mdh.factory.BookFactory"></bean>

实现FactoryBean接口

public class BookFactory implements FactoryBean<Book> {
    @Override
    public Book getObject() throws Exception {
        return new Book();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }
}

在getBean时,需要用Book对象接收

什么是工厂模式

某个特定的类型对象由工厂统一生产,以达到对对象的统一管理

一些Bean标签

1.lazy-init:true懒加载(延迟加载)false立即加载

2.init-method:设置对象初始化阶段调用的方法

3.destory-method:设置对象销毁阶段调用的方法

4.scope:设置作用域

5、bean作用域、生命周期与自动装配

bean作用域

前两个是SpringCore项目都可以用,后三个是基于Web的项目可用

1.singleton

描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例

2.prototype

描述:每次对该作⽤域下的Bean的请求都会创建新的实例

3.request

描述:每次http请求会创建新的Bean实例,类似于prototype

场景:⼀次http的请求和响应的共享Bean

4.session

描述:在⼀个http session中,定义⼀个Bean实例

场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息

5.application

描述:在⼀个http servlet Context中,定义⼀个Bean实例

场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息

bean的生命周期

所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程

1.通过构造器创建bean实例(执行构造器)

2.为bean赋值(执行set方法)

3.把bean实例传递给bean的前置处理器方法

4.初始化bean(调用bean的初始化方法)

5.把bean实例传递给bean的后置处理器方法

6.bean的获取(getBean方法)

7.容器关闭并销毁bean(调用销毁方法)

通知处理器的使用

<!-- 配置后置通知处理器 -->
<bean id="myBeanProcessor" class="com.mdh.demo.MyBeanProcessor"></bean>

对于所有bean都会影响

public class MyBeanProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //Object bean 要干预的bean
        //String beanName bean的id
        return null;
 }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return null;
    }
}

bean的自动装配

自动注入bean,例如

public class Score{
    private int java;
    private int cpp;
}
public class Student {
    private String name;
    private Score score;
}

autowire属性控制自动将容器中的对象注入到当前对象的属性上

byName根据目标id值和属性值注入,要保证当前对象的属性值和目标对象的id值一致

byType根据类型注入,要保证相同类型的目标对象在容器中只有一个实例

<bean id="score" class="com.mdh.demo.Score"></bean>
<bean id="student" class="com.mdh.demo.Student" autowire="byName"></bean>

6、引入外部属性配置文件

在resources目录下用.properties结尾的文件来配置

username="root"
pa55word="123456"

在xml里就可以引入

<!-- 读取jdbc.properties配置文件 -->
<context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
	<property name="username" value="${username}"></property>
    <property name="password" value="${password}"></property>
</bean>

7、注解方式管理bean

注解方式实现IOC

@Component【组件类】

该注解有四个子注解

(1)@Controller【控制器】

(2)@Service【业务逻辑类】

(3)@Repository【数据持久类】

(4)@Configuration【配置类】

第一步:在xml文件里开启注解扫描

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/context
 http://www.springframework.org/schema/context/spring-context.xsd">
	<!-- 设置为true,则使用默认扫描过滤器,可以识别并包含所有五个注解 -->
    <!-- 设置为false,则自己设置扫描哪些注解 -->
    <context:component-scan base-package="com.mdh.demo" use-default-filters="false">
    <!-- include 控制只扫描哪些注解-->
    <!-- exclude 控制不扫描哪些注解-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>
</beans>

第二步:在类上添加注解,让spring容器给我们创建bean实例并存储于容器中

@Component("user1")
//可以命名id,
//如果不命名,默认是类首字母小写
public class User {
    private int uid;
    private String username;
    private String password;
}

注解方式实现DI

1.对象注入

例如在service层调用dao层

使用Autowired注解注入,如果有多个实现类,则可以配合Qualifier注解使用

也可以使用Resource实现

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    @Qualifier("userDaoImplB")
    //@Resource(name="userDaoImplB")
    private UserDao userDao;

    @Override
    public void add(){
        userDao.add();
    }
}

2.注入基础数据类型(8+String)

@Value("zhangsan")
private String name;
@Value("1")
private Integer age;

也可以通过外部属性配置文件注入

首先配置properties文件

name="张三"
age=1

其次配置xml

 <context:property-placeholder location="classpath:*.properties"></context:property-placeholder>

然后就可以使用了(可能出现中文乱码问题)

@Value("${name}")
private String name;
@Value("${age}")
private Integer age;

配置类方式实现IOC和DI

可以通过ComponentScan配置扫描路径

也可以通过PropertySource注入外部属性配置文件

@ComponentScan(basePackages = "com.mdh")
@PropertySource("classpath:aaa.properties")
public class SpringConfig{
}

在使用时,就从此类的字节码文件中获取spring上下文对象

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("SpringConfig.class");
        User user = context.getBean("user1",User.class);
        System.out.println(user);
    }
}

8、代理模式

什么是代理模式

通过代理对象访问目标对象,这样可以在目标对象的基础上增强额外的功能,如添加权限、访问控制和审计等功能

相当于中介或者律师

代理模式的作用

在不修改或者没有办法修改原有代码的情况下,增强对象功能,使用代理对象代替原来的对象去完成功能进而达到拓展功能的目的

静态代理

静态代理中代理类与被代理类都需要实现同一接口,这就说明一个静态代理类只能代理一个类,并且还要事先知道我们要代理哪个类才能写代理类,如果我们有其他类还想使用代理那就必须再写一个代理类。然而在实际开发中我们是可能有非常多的类需要被代理的,并且事先我们可能并不知道我们要代理哪个类,所以如果继续使用静态代理反而会增加许多的工作量,并且效率低下,代码复用率也不高

动态代理

动态代理可以针对一些不特定的类或者一些不特定的方法进行代理,我们可以在程序运行时动态的变化代理的规则,代理类在运行时才创建的代理模式称为动态代理,这种情况下,代理类并不是在Java代码中定义好的,而是在程序运行时根据我们在Java代码中的“指示”动态生成的

Proxy动态代理:JDK动态代理(面向接口)

cglib动态代理:第三方动态代理(面向父类)

JDK动态代理

面向接口

1.一定要有接口和实现类的存在,

2.代理对象只能增强接口中的定义的方法

3.代理对象只能读取到接口中方法上的注解

public class Test {
    public static void main(String[] args) {
        Dinner dinner = new Person("张三");

        ClassLoader classLoader = dinner.getClass().getClassLoader();
        Class[] interfaces = dinner.getClass().getInterfaces();
        InvocationHandler invocationHandler = new InvocationHandler() {
            @Override
            //当我们让代理对象调用任何方法时,都会触发invoke方法执行
            //(1)Object proxy,代理对象,这里是dinnerProxy
            //(2)Method method,被代理的方法,这里是eat方法或者drink方法
            //(3)Object[] args,被代理方法运行时的实参,这里是“饭”或者“茶”
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //接收方法的返回值
                Object res = null;
                //我们这里对eat方法进行增强,对drink方法不增强
                if (method.getName().equals("eat")){
                    System.out.println("饭前洗手");
                    //执行原有的eat方法
                    res = method.invoke(dinner,args);
                    System.out.println("饭后刷牙");
                }else{
                    //只用执行原本的drink方法
                    res = method.invoke(dinner,args);
                }
                return res;
            }
        };

        //通过Proxy动态代理获得一个代理对象,在代理对象中,对某个方法进行增强
        //(1)ClassLoader loader,被代理对象的类加载器,这里为dinner的类加载器
        //(2)Class<?>[] interfaces,被代理对象所实现的所有接口,这里为Dinner接口
        //(3)InvocationHandler h,执行处理器对象,专门用于定义增强规则,在这里写增强的策略
        Dinner dinnerProxy = (Dinner)Proxy.newProxyInstance(classLoader,interfaces,invocationHandler);

        dinnerProxy.eat("饭");
        dinnerProxy.drink("茶");
    }
}

@AllArgsConstructor
class Person implements Dinner{
    private String name;
    @Override
    public void eat(String food) {
        System.out.println(name+"正在吃"+food);
    }
    @Override
    public void drink(String food) {
        System.out.println(name+"正在喝"+food);
    }
}

interface Dinner{
    void eat(String food);
    void drink(String food);
}

cglib动态代理

面向父类

1.和接口没有直接关系

2.不仅仅可以增强接口中定义的方法,还可以增强类中定义的方法

3.可以读取父类中方法上的所有注释

public class Test2 {
    public static void main(String[] args) {
        Student student = new Student("张三");

        //1.获得一个Enhancer对象
        Enhancer enhancer = new Enhancer();
        //2.设置父类字节码
        enhancer.setSuperclass(student.getClass());
        //3.获取MethodInterceptor对象,用于定义增强规则
        MethodInterceptor methodInterceptor = new MethodInterceptor() {
            //Object o,生成之后的代理对象,就是我们接下来的studentProxy
            //Method method,父类中原本要执行的方法,也就是Student里的eat方法
            //Object[] objects,方法调用时传入的实参数组
            //MethodProxy methodProxy,子类中重写父类的方法,也就是studentProxy里的eat方法
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //接收方法的返回值
                Object res = null;
                //我们这里对eat方法进行增强,对drink方法不增强
                if (method.getName().equals("eat")){
                    System.out.println("饭前洗手");
                    //执行原有的eat方法
                    res = methodProxy.invokeSuper(o,objects);
                    System.out.println("饭后刷牙");
                }else{
                    //只用执行原本的drink方法
                    res = methodProxy.invokeSuper(o,objects);
                }
                return res;
            }
        };
        //4.设置MethodInterceptor
        enhancer.setCallback(methodInterceptor);
        //4.获得代理对象
         Student studentProxy = (Student)enhancer.create();
        //5.使用代理对象完成功能
        studentProxy.eat("饭");
        studentProxy.drink("茶");
    }
}

@NoArgsConstructor
@AllArgsConstructor
class Student {
    private String name;
    public void eat(String food){
        System.out.println(name+"正在吃"+food);
    }
    public void drink(String food){
        System.out.println(name+"正在喝"+food);
    }
}

9、AOP的概念和原理

什么是AOP

AOP是面向切面编程,一般可以帮助我们在不修改现有代码的情况下,对程序的功能进行拓展,往往用于实现日志处理,权限控制,性能检测,事务控制等

AOP实现原理

实现原理就是动态代理,在有接口的情况下,使用JDK动态代理,没有接口的情况下,使用cglib动态代理

AOP的一些术语

1.连接点 Join Point

可以被增强的方法

2.切入点 Pointcut

实际被增强的方法

3.通知 Advice

实际增强的功能

4.目标对象Target

被代理的对象

5.切面Aspect

功能相关的advice方法放在一起声明成的一个Java类

6.织入 Weaving

创建代理对象并实现功能增强的声明并运行的过程

AOP五个注解

1.@Before:前置通知

切点方法执行之前先执行的功能

2.@After:后置通知

方法执行之后要增强的功能

3.@AfterReturning:返回通知

切点方法return之后增强的功能,切点方法如果出现异常则不执行

4.@AfterThrowing:异常通知

切点方法出现异常就执行,不出现异常就不执行

5.@Around:环绕通知

切点方法之前和之后都能进行功能的增强,可以控制切点方法的执行

环绕通知的返回值必须是Object,在通知中必须要将切点方法继续向上返回

10、AOP注解方式的实现

注解方式实现AOP

1.导入依赖包

<!-- spring切面包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.5</version>
</dependency>
<!--aop联盟包-->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>

2.通过@Aspect注解声明切面

@Aspect
@Component
//如果有多个增强类对同一方法进行增强,可以通过此注解设置优先级
//数字越小,代理位置越靠近注入位置,优先级越高
@Order(1)
public class DaoAspect {

    //定义一个公共切点,可以指向方法或者接口
    @Pointcut("execution(* com.mdh.mvc.dao.UserDao.add(..))")
    public void addPointCut(){}

    @Before("addPointCut()")
    public void methodBefore(JoinPoint joinPoint){
        //可以获取方法执行的参数
        Object[] args = joinPoint.getArgs();
        System.out.println(Arrays.toString(args));
    }

    @After("addPointCut()")
    public void methodAfter(JoinPoint joinPoint){

    }

    @AfterReturning(value = "addPointCut()",returning = "res")
    public void methodAfterReturning(JoinPoint joinPoint,Object res){
        //可以接收方法的返回值
        System.out.println(res);
    }

    @AfterThrowing(value = "addPointCut()",throwing = "e")
    public void methodAfterThrowing(Exception e){
        //可以获取异常信息
        System.out.println(e.getMessage());
 }

    @Around("addPointCut()")
    public Object methodAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //切点方法在这里执行
        Object res = proceedingJoinPoint.proceed();
        return res;
    }
}

JoinPoint对象

JoinPoint对象封装了SpringAOP中切面方法的信息

方法名功能
Signature getSignature();获取封装了署名信息的对象,在该对象中可以获取到目标方法名,所属类的Class等信息
Object[] getArgs();获取传入目标方法的参数列表
Object getTarget();获取被代理的对象
Object getThis();获取代理对象

ProceedingJoinPoint对象

是对象JoinPoint的子接口,只用于@Around的切面方法中,添加了两个方法

Object proceed();执行目标方法

Object proceed(Object[] varl);传入的新的参数去执行目标方法

通过配置类

//声明是一个配置类
@Configuration
//要扫描的包
@ComponentScan("com.mdh")
//是否自动生成切面代理对象
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class SpringConfig{

}

11、事务

事务的概念

事务指的是一个操作序列,该操作序列中的多个操作,要么都做,要么都不做,是一个不可分割的单位

事务的四大特性

1.原子性

事务是最小的执行单位,不允许分割,事务的原子性确保动作要么全部执行,要么全部不执行

2.一致性

执行事务的前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变的

3.隔离性

各个事务的执行互不干扰

4.持久性

一个事务被提交后,他对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响

事务的并发问题

1.脏读

事务B修改数据后回滚,期间事务A读到的数据就是脏读

时间点事务A事务B
1开启事务A
2开启事务B
3读 A = 10
4写 A = 20
5读 A = 20
6事务回滚

2.不可重复读

事务B修改数据导致事务A前后两次读取不一致

时间点事务A事务B
1开启事务A
2开启事务B
3读 A = 10
4写 A = 20
5读 A = 20

3.幻读

一个事务两次读取,中间进行了插入操作

时间点事务A事务B
1开启事务A
2开启事务B
3size = N
4插入一条数据
5size = N+1

事物的隔离级别

隔离级别脏读不可重复读幻读
未提交读×××
提交读××
可重复读×
可串行化

可串行化:一个事务执行完,另一个事务才能执行

12、日志和测试

日志的级别

过滤规则:比日志设置级别低的信息就会忽略掉

trace:轻微痕迹,级别最低;

debug:调试日志;

info:普通信息,默认级别;

warn:警告,不影响使⽤,但需要注意的问题;

error:错误信息;

fatal:致命,代码异常导致程序退出执⾏的事件,级别最高;

使用日志框架

添加依赖

<!-- spring切面包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.5</version>
</dependency>
<!--aop联盟包-->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>

在resource目录下创建log4j2.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="DEBUG">
    <Appenders>
        <!-- OUT打印黑色字体,ERR是红色 -->
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
        </Console>
    </Appenders>
    <Loggers>
        <!-- 打印日志级别 -->
        <Root level="debug">
            <AppenderRef ref="Console"/>
        </Root>
    </Loggers>
</Configuration>

junit5单元测试

导入依赖包

<!-- junit5 -->
<dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.7.0</version>
        <scope>test</scope>
</dependency>

添加测试代码

//配置扫描的xml文件
@SpringJUnitConfig(locations = "classpath:applicationContext.xml")
public class Test{
    @Autowired
    private UserBean userBean;
	@Test
	public void testUserBean(){

	}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值