当前位置: 首页 > news >正文

Java 面试八股文 —— SSM 框架常见面试题

前言:

       本文章主要用于记录博主准备面试的题目记录,有哪个题目有问题或者解释不清的,还请及时下方留言,感谢支持!!!

目录:

  • 1、常见的 ORM 框架有哪些
  • 2、Bean 容器 / Ioc 容器的理解
  • 3、Ioc / DI 的理解
  • 4、Spring 中单例 bean 的线程安全问题
  • 5、Spring 中 bean 的作用域
  • 6、FactoryBean和BeanFactory
  • 7、Bean 的生命周期
  • 8、Spring 三级缓存的理解【Spring 中如何解决循环依赖】
  • 9、Spring AOP 的理解
  • 10、Spring 事务的隔离级别
  • 11、Spring 事务中有哪几种事务传播行为
  • 12、Spring MVC 流程
  • 13、MyBatis 中 # 和 $ 的区别
  • 14、MyBatis 如何一对一、一对多关联
  • 15、SpringBoot 自动配置原理

1、常见的 ORM 框架有哪些

①、首先 ORM 为:

  • 对象关系映射模式,为了解决面向对象和关系数据库存在互不匹配的现象的技术,它是通过使用描述对象和数据库之间的关系的元数据,将程序中的对象自动持久化存储到数据库中。至于如何实现持久化,一种简单的方式就是通过硬编码的方式,为每一种数据库访问操作提供单独的方法

此种方法有以下不足:
Ⅰ、持久层缺乏弹性,不能响应需求的变化,修改变化随之就需要修改持久层的接口
Ⅱ、持久层同时和域模型与关系数据库模型绑定,一旦域模型或者是关系数据库模型改变,毒药修改持久层的代码,增加软件的维护成本

  • ORM 提供了另外一种实现持久化的模式,它采用映射元数据的方式来描述对象关系的映射。使得任意 ORM 中间件能在任何一个业务逻辑层和数据持久层起到桥梁的作用
  • ORM 基于三个核心原则:简单、传达性、精确性,其优点是:提高开发效率,由于 ORM 可以自动对对象和数据库中的 table 表中的字段进行映射,所以不需要一个庞大的数据访问层,同时可以像操作对象一样从数据库中获取数据。同时其也存在缺点,ORM 会牺牲程序效率和固有思维模式,采用 ORM 的系统一般都是多层系统,系统的层数多了,效率就会降低。ORM 是一种完全面向对象的做法,而面向对象也会对性能产生一定的影响

②、Java 中常见的 ORM 框架:

Ⅰ、MyBatis 半自动的 ORM 框架,需要手动去实现 sql,再由框架根据 sql 及 sql 传入的数据来组装为要执行的 sql。其优点为:

  • ①、程序员手动写 sql 学习成本低
  • ②、更方便做 sql 性能的优化和维护
  • ③、对关系型数据库模型要求不高,这样在做数据库调整时,影响不会太大,适合比较频繁变动需求的系统,因此国内大部分系统都是使用的半自动的 ORM 框架
  • Mybatis 的缺点:不能跨数据库,因为写的 sql 存在某种数据库的特有的语法或关键词

Ⅱ、Hibernate 全自动的 ORM 框架,不需要手动实现 sql ,调用框架提供的 API,可以自动将对象组装为要执行的 sql,其优点为:

  • ①、可以跨数据库
  • ②、全自动 ORM 框架,自动组装为 sql 语句
  • 其缺点为:学习门槛高,要学习框架 API 与 sql 的转换关系
  • 对数据库的模型依赖非常大,在软件需求的频发变更中,会导致维护的难度高,很难定位问题,也很难进行性能优化,需要精通框架,对数据库的框架需要十分熟悉

2、Bean 容器 / Ioc 容器的理解

Spring 容器是对 Ioc 设计模式的实现,使用统一的容器去存储 Bean 对象,及管理对象的依赖关系,创建容器的 API 主要是 BeanFactory 和 ApplicationContext 两种:

  • 1、BeanFactory 是最底层的容器接口,只有最基础的容器功能,Bean 对象实例化和依赖注入,并且使用懒加载的方式,这意味着 bean 对象只有在通过 getBean 方法直接调用的时候他们才进行实例化
  • 2、ApplicationContext (上下文),是 BeanFactory 的子接口,为预加载,每一个 bean 对象都在 ApplicationContext 启动时,进行实例化,除了基础功能还具备以下功能:

①、整合了 bean 的生命周期管理
②、国际化功能
③、载入多个上下文关系(存在继承关系),使得上下文都专注于一个特定的层次,比如应用的 web 层
④、事件发布响应机制
⑤、AOP

两种 API 创建容器的方式:

  • 1、方法一:ApplicationContext 扫描的路径是 spring 配置文件的路径,再通过 context 去获取 bean 对象即可
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
  • 2、方法二、 BeanFactory,同样的扫描路径以及通过 factory 的 getBean 方法获取到 bean 对象
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("spring-config.xml"));

3、Ioc / DI 的理解

  • Ioc 中文含义为 “控制反转”,我们在面向对象设计的软件系统中,其底层是由多个对象构成的,各个对象通过相互合作,完成最终的业务逻辑
    在这里插入图片描述
  • 看上图的齿轮图,四个齿轮分别代表四个对象,这四个齿轮相互咬在一起转动,任何一个齿轮发生故障都有可能导致整个系统的崩溃,齿轮之间的咬合关系就是程序中的对象的耦合关系十分相似,对象的耦合是必然存在无法避免的,这是协同工作的基础,随着软件的规模的增大,对象间的依赖关系越来越复杂,对于对象耦合度过高的系统,就会容易出现牵一发而动全身的情形
  • Ioc 提出的理论大体就是这样的:借助第三方实现依赖关系的对象的解耦,再看下一张的齿轮图:
    在这里插入图片描述
  • 四个有颜色的齿轮代表四个对象,中间的大齿轮就是 Ioc 容器,此容器就使得 A B C D 四个对象没有了依赖,所有的齿轮的转动都是依靠着 Ioc 容器的转动(第三方),所有齿轮的控制权都上交给了第三方(Ioc 容器),所以 Ioc 就称为整个系统的关键核心,他起到了一种类似粘合剂的作用,把所有的对象粘合在一起发挥作用,如果没有整个粘合剂,对象和对象之间就会失去联系(这就是控制反转)

在这里插入图片描述

  • 反过来再看这个齿轮系统,去掉 Ioc 容器,此时的四个对象的耦合度已经降到了最低,当实现 A 时无需求去关心 B C D 的实现,对象之间的依赖程度已经降到了最低,对开发系统是一件非常好的事情
  • 既然 Ioc 是控制反转,那么到底是在哪些方面的控制被反转了?

即是在依赖对象的时候被反转了,控制反转之后,获取依赖对象的过程由自身管理转换为由 Ioc 容器主动注入,所以在 Ioc 容器在运行时,动态的将依赖关系,注入到对象之中被称为:“依赖注入”(DI)

  • 依赖注入(DI),控制反转(Ioc)是从不同角度去描述同一件事情,通过引入 Ioc 容器和依赖注入,实现对象间的解耦合,DI 是 Ioc 思想的一种实现,具体实现方式:属性注入、setter注入、构造方法注入,实现原理:反射及ASM字节码框架实现。具体依赖注入的代码:

1、属性注入:(此示例是在 userController 类中注入一个 userService 类)

@Autowired
    private UserService userService;

2、setter 注入:

@Autowired // 必须要添加
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

3、构造方法注入:

@Autowired
    public UserController2(UserService userService) {
        this.userService = userService;
    }

4、三种注入方式的优缺点:

  • ①、属性注入:简洁方便使用,只能用于 Ioc 容器,并且在使用时才会报出空指针异常
  • ②、构造方法注入:spring 官方的推荐注入方式,在使用时一定保证注入的类不能为空,多个注入会显得程序比较臃肿
  • ③、setter 注入通用性不如构造方法注入

5、两种注入方式: @Resource / @Autowired 二者区别:

  • Autowired 来自 spring、Resource 来自 JDK
  • 作用范围不同:Resource 不能进行构造方法注入,Autowired 三种注入方法都可以
  • Resource 支持更多参数的设置

4、Spring 中单例 bean 的线程安全问题

单例 bean 存在线程安全问题,当多线程同时操作一个同一对象时,对这个对象的非静态成员变量的写操作会存在线程安全问题。常见的有两种解决方法:

  • 在对象中尽可能定义可变的成员变量(不现实)
  • 在类中定义一个 ThreadLocal 类将需要的可变成员变量保存到 ThreadLocal 中(推荐)

关于 ThreadLocal:

①、ThreadLocal 是什么

ThreadLocal 为本地线程变量,其中填充的是当前线程的变量,该变量对于其他线程来说是封闭且隔离的。ThreadLocal 为变量在每个线程中创建了一个副本,这样每个线程都可以访问到自己的副本变量

1、在进行对象跨层传递时,使用 ThreadLocal 可以避免多次传递,打破层次间的约束
2、线程间数据隔离
3、进行事物操作,用于存储线程事物信息
4、数据库连接,session 会话管理

②、ThreadLocal怎么用

public static void main(String[] args) {
        ThreadLocal<String> local = new ThreadLocal<>();

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    local.set(Thread.currentThread().getName() + ":" + finalI);
                    System.out.println(Thread.currentThread().getName() + ":" + finalI);
                }
            });
            thread.start();
        }
    }
  • 运行结果:可以看到每一个线程都有自己的 local 值
线程:Thread-0,local:Thread-0:0
线程:Thread-1,local:Thread-1:1
线程:Thread-2,local:Thread-2:2
线程:Thread-3,local:Thread-3:3
线程:Thread-4,local:Thread-4:4
线程:Thread-5,local:Thread-5:5
线程:Thread-6,local:Thread-6:6
线程:Thread-7,local:Thread-7:7
线程:Thread-8,local:Thread-8:8
线程:Thread-9,local:Thread-9:9

③、ThreadLocal源码分析

  • set 原码:ThreadLocalMap 为 ThreadLocal 的静态内部类 定义 Entry 来保存数据,继承是弱引用,在 Entry 中使用 ThreadLocal 作为 key ,使用我们自己设置的 value 作为 value ,对于每个线程内部有个ThreadLocal.ThreadLocalMap 变量,存取值的时候,也是从这个容器中来获取
/**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        //首先获取当前线程对象
        Thread t = Thread.currentThread();
        //获取线程中变量 ThreadLocal.ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //如果不为空,
        if (map != null)
            map.set(this, value);
        else
            //如果为空,初始化该线程对象的map变量,其中key 为当前的threadlocal 变量
            createMap(t, value);
    }

    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
//初始化线程内部变量 threadLocals ,key 为当前 threadlocal
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

       /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }


 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

2、get 方法:同过 ThreadLocal (当前的线程)作为 key 去查询 value 值

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

④、ThreadLocal 内存泄漏问题:先看下面的类

/**
         * The entries in this hash map extend WeakReference, using
         * its main ref field as the key (which is always a
         * ThreadLocal object).  Note that null keys (i.e. entry.get()
         * == null) mean that the key is no longer referenced, so the
         * entry can be expunged from table.  Such entries are referred to
         * as "stale entries" in the code that follows.
         */
        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
  • 注释中 Note that null keys (i.e. entry.get() * == null) 如果 key 值为空,这个 entry 就可以清除掉了,ThreadLocal是一个弱引用,当为null时,会被当成垃圾回收 。但是 ThreadLocalMap 为静态成员变量(和 Thread 的生命周期一样),他不会回收,那就是 ThreadLocalMap 的 key 值为 null 但是 value 值还是存在,这就造成了内存泄漏
  • 解决办法:使用完 ThreadLocal 后,执行 remove 操作避免内存溢出情况,清除当前线程的数值

⑤、为什么 key 为弱引用

  • 如果是强引用,当 ThreadLocal 的对象引用被回收后,ThreadLocalMap 还持有 ThreadLocal 的强引用,如果没有删除这个 key ,那么 ThreadLocal 就不会被回收,只要线程还存在 ThreadLocalMap 引用的那些对象就不会被回收,可以认为这导致 Entry 内存泄漏

附:强弱引用

  • 强引用:普通的引用,强引用指向的对象不会被回收
  • 软引用:仅有软引用指向的对象,只有发生 gc 且内存不足,才会被回收
  • 弱引用,仅有弱引用指向的对象,只要发生 gc 就回收

5、Spring 中的 bean 对象的作用域

  • ①、singleton:唯一的 bean 实例,spring 中的 bean 默认都是单例
  • ②、prototype:每次请求都创建一个新的 bean 实例
  • ③、request:每次一个 HTTP 请求都会产生一个新的 Bean 该 bean 仅在当前的 HTTP request 有效
  • ④、session:每次一个 HTTP 请求都会产生一个新的 Bean 该bean 仅在当前的 HTTP session 有效
  • ⑤、application:在一个引用 servlet 上下文生命周期中,产生一个新的 bean
  • ⑥、websocket:在一个 websocket 生命周期中,产生一个新的 bean

6、FactoryBean和BeanFactory

  • BeanFactory 是 spring 中的顶级接口,所有的 bean 对象都是通过 BeanFactory 进行管理的。
  • FactoryBean 是实例化一个 bean 对象的工厂类实现了 FactoryBean 接⼝的 Bean 根据该 bean 的 id 从BeanFactory中获取的实际上是FactoryBean中 getObject() ⽅法返回的对象,⽽不是 FactoryBean本身,如果要获取FactoryBean对象,请在id前⾯加⼀个 & 符号来获取

7、Bean的⽣命周期

  • ①、实例化 Bean (通过反射调用构造方法实现对象的实例化)
  • ②、依赖注入(实现对象的属性)
  • ③、Bean 的初始化
    • Ⅰ、执行各种 Aware
    • Ⅱ、执行 BeanPostProcessor 初始化前置方法
    • Ⅲ、执行 @PostConstruct 方法,依赖注入后被执行
    • Ⅳ、执行自己的 init_method 方法
    • Ⅴ、执行 BeanPostProcessor 初始化后置方法
  • ④、使用 Bean
  • ⑤、销毁 Bean
    • Ⅰ、执行销毁前方法 @PreDestory
    • Ⅱ、如果有接口 DisposableBean 执行实现方法
    • Ⅲ、执行销毁前的方法 destory-method

8、Spring 三级缓存的理解【Spring 中如何解决循环依赖】

  • 至于什么是循环依赖:就是 A 对象依赖 B 对象,B 对象又依赖 A 对象,类似于下面的代码:
@Component
public class A{
    @Autowired
    private B b;
}
@Component
public class B{
    @Autowired
    private A a;
}
  • A 在实例化后,发现需要依赖 B ,当前 spring 容器中没有 B 对象,就会先去实例化 B 对象,在实例化 B 对象时,发现需要依赖 A 对象,又会继续去实例化 A ,这样会一直反复,这就套娃了, 你猜是先 StackOverflow 还是 OutOfMemory ?Spring怕你不好猜,就先抛出了BeanCurrentlyInCreationException,这就是循环依赖问题,这里不叫死锁(叫法不一样),在 spring boot 3.0 之后就关闭了循环依赖的功能,所以这样的代码只有在 spring boot 3.0 之前写不会报错,至于官方是如何解决循环依赖问题的,这里就使用到了三级缓存
  • 这里回顾一下,上面 bean 对象的生命周期:
  • 实例化 Bean
  • 依赖注入:装配 Bean 的属性
  • 初始化:执行各种 aware 方法,执行初始化前置方法,执行初始化方法,执行初始化后置方法
  • 使用 bean
  • 销毁 bean
  • spring 保存 bean 对象的方式采用的是缓存的方式,使用 Map<String, Object> 的结构,key 为 bean 的名字,Object 为 bean 对象
  • 在 spring 容器中注册循环依赖的 bean 必须是单例模型,且依赖注入的方式为属性注入,原型模型或构造方法注入都是不行的
  • spring 解决循环依赖,采用如下图的三级缓存(每一级缓存都是一张哈希表):
    在这里插入图片描述
  • 在这三级缓存中,每一个对象只能在存在于一个缓存中,其他缓存都会被删掉。查找缓存是从1☞3,存储对象是 3 ☞ 1
  • 那么针对上面的循环依赖的问题,三级缓存又是怎样去解决的?

首先实例化 A 对象,将其放入到三级缓存中,依赖注入 B,在缓存中寻找 B 发现是找到不的,此时的 A 是一个半成品,将 A 放入到 二级缓存中,去实例化 B ,将 B 放入到三级缓存,后依赖注入 A 可以在二级缓存中找到 A,那么将此时的 A 对象指向二级缓存中的 A 的地址,虽然 A 是一个半成品,但是我们仅仅只是把引用指向了缓存中的 A ,后 B 再进行初始化,将 B 放入到一级缓存中,最后进行 A 对象可以继续向下执行,在一级缓存中找到了 B ,后再执行 A 的初始化方法,将 A 放入到一级缓存中,此时循环依赖的问题就被解决了

  • 那么就抛出了一个问题,三级缓存可以解决循环依赖的问题,那么二级缓存能否可以解决?

答案是不行的,这三个缓存中存储的内容是不一样的,当一个 Bean 开始实例化时,是存放在三级缓存中的,当加载到一半时,就会放到二级缓存,将三级缓存的数据清除,而二级缓存又被分为两部分,二级缓存这里多了 AOP 的问题,导致二级缓存变复杂了,如果没有 AOP 这一环节,二三级是可以合并在一起的。最后一个完整的 bean 对象肯定是需要一个单独的缓存来进行存储,所以三级缓存是必要的,下面的是更细一点的循环依赖的解决:

在这里插入图片描述


9、Spring AOP 的理解

  • AOP :面向切面编程,对于多个业务逻辑横切来实现统一的代码管理,而不用切入代码本身,这样面向切面编程的思想就被称为 AOP(一种思想),AOP 是对某一类事件进行处理,让后面程序没有后顾之忧去写程序,比如博客查询,你就单纯去写查询博客的代码,不用考虑登录情况检查,直接 AOP 解决

  • Ⅰ、切面:定义 AOP 针对哪一个的功能的,这个功能就叫做一个切面,比如用户登录,方法日志统计,切面是由切点和通知组成

  • Ⅱ、连接点:可以触发 AOP 的点,就是连接点,需要被增强的某个 AOP 功能的所有方法

  • Ⅲ、切点:定义 AOP 拦截规则

  • Ⅳ、通知:规定 AOP 执行的时期和执行的方法

  • 实现步骤:

  • Ⅰ、引入 Spring-AOP 依赖

  • Ⅱ、定义切面

  • 使用场景:日志记录,异常控制,事务管理,安全控制,性能统计

  • 优点:代码解耦合,统一功能业务对代码没有入侵,可扩展能力强,灵活性高

  • springAOP 是采取动态代理的方式具体是基于 jdk 和 CGLIB 两种:

    • ①、jdk 动态代理:需要被代理类实现接口, InvocationHandler 和 Proxy 动态的⽣成代理类
    • ②、CGLIB 动态代理:需要被被代理类可以被继承,不能被 final 修饰,使用 MethodInterceptor 来对方法拦截,CGLIB 底层是基于 ASM 框架的,在运行时动态生成代理类
  • SpringAOP 如何使用,@Aspect 定义切面,point 定义切点方法后,可以对目标方法进行拦截

    • 前置通知:@Before 通知方法会在目标方法调用之前执行
    • 后置通知:@After 通知方法会在目标方法返回或者抛出异常后调用
    • 返回之后通知:使⽤@AfterReturning:通知⽅法会在⽬标⽅法返回后调⽤。
    • 抛异常后通知:使⽤@AfterThrowing:通知⽅法会在⽬标⽅法抛出异常后调⽤。
    • 环绕通知:使⽤@Around:通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执⾏⾃定义的⾏为。
  • 代码实例:定义切面、定义切点、定义通知

@Aspect
@Component
public class UserAspect {
    @Pointcut("execution(* com.example.demo.controller.UserController.*(..))")
    public void pointcut(){}

    @Before("pointcut()")
    public void doBefore(){
        System.out.println("执行前置方法");
    }

    @After("pointcut()")
    public void doAfter(){
        System.out.println("执行后置方法");
    }

    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        System.out.println("执行 AfterReturning");
    }

    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){
        System.out.println("执行 AfterThrowing");
    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        Object o = null;
        System.out.println("执行 Around");
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        try{
            o = joinPoint.proceed();
        }catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        stopWatch.stop();
        System.out.println("Around 执行结束");
        System.out.println("执行时间:" + );
        return o;
    }
}
  • 具体的被代理类:
@RestController
public class UserController {
    @RequestMapping("/sayhi")
    public String sayHi(){
        System.out.println("你好世界");
        return "你好世界";
    }
}

10、Spring 事务的隔离级别

Spring 事物一共五种隔离级别:

  • ①、ISOLATION_DEFAULT:使用后端数据库的默认隔离级别,mysql 默认采用 REPEATABLE_READ 隔离级别
  • ②、ISOLATION_READ_UNCOMMITTED:读未提交,允许读取尚未提交的数据变更,可能会导致不可重复读,脏读,幻读的问题
  • ③、ISOLATION_READ_COMMITTED:读已提交,允许读取并发事务已经提交的数据,可以阻止脏读问题,但是可能会有不可重复读,幻读的问题
  • ④、ISOLATION_REPEATABLE_READ:重复读,同一字段多次读取到的结果都是一样的,避免了脏读,不可重复读,但是会有幻读的问题
  • ⑤、ISOLATION_SERIALIZABLE:最高隔离级别,完全服从 ACID 隔离界别,所有事务逐个执行,这样事务之间就完全不可能产生干扰,该级别可以阻止幻读、可重复读、脏读问题,但是程序的性能也会大大降低,通常情况下不会使用该隔离级别

11、Spring 事务的传播机制

在 TransactionDefinition 中定义了七种表示事物传播行为的常量

  • 支持当前事物的情况:

    • ①、PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务,如果当前事务不存在,则创建一个新的事务
    • ②、PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务,如果当前事务不存在,则以非事务的方式继续运行
    • ③、PROPAGATION_MANDATORY:如果当前事务存在,则加入该事务,如果当前事务不存在,则抛出异常
  • 不支持当前事务:

    • ①、PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
    • ②、PROPAGATION_NOT_SUPPORTED:以非事务的方式运行,如果当前存在事务,就将当前事务挂起
    • ③、PROPAGATION_NEVER:以非事务的方式运行,如果当前存在事务,就抛出异常
  • 其他情况:

    • ①、PROPAGATION_NESTED:如果存在事务,则创建一个事务作为当前事务的嵌套事务运行,如果当前没有事务,则该取值等价为 PROPAGATION_REQUIRED

12、Spring MVC 流程

在这里插入图片描述

  • 流程:
    • ①、(发起)发起请求到前端控制器:DispatcherServlet
    • ②、(查找)前端控制器请求 HandlerMappering 查找 Handler (可以根据 xml / 注解进行查找)
    • ③、(返回)处理器映射器 HandlerMappering 向前端控制器返回 Handler ,HandlerMapping 会把请求映射为 HandlerExecutionChain 对象 (包含一个 Handler 处理器(页面控制器)对象,多个 HandlerInterceptor 拦截器对象),通过这种策略很容易添加新的映射策略
    • ④、(调用)前端控制器去调用处理器适配器去执行 Handler
    • ⑤、(执行)处理器适配器HandlerAdapter将会根据适配的结果去执⾏Handler
    • ⑥、(返回)Handler 执行完给适配器返回 ModelAndView
    • ⑦、(接收)处理器控制器向前端控制器返回 ModelAndView (SpringMVC 的底层对象,包括 model 和 view)
    • ⑧、(解析)前端控制器请求视图解析器去进⾏视图解析 (根据逻辑视图名解析成真正的视图(jsp)),通过这种策略很容易更换其他视图技术,只需要更改视图解析器即可
    • ⑨、(返回)视图解析器向前端控制器返回 view
    • ⑩、(渲染)前端控制器进⾏视图渲染 (视图渲染将模型数据(在ModelAndView对象中)填充
      到request域)
    • ⑪、(响应)前端控制器向⽤户响应结果
  • 上述流程返回的是一个静态页面,还有一种执行模式,缺少 8 ~ 10 的步骤,返回的就是一个 普通的数据。简单来说就是一个没有使用 @ResponseBody 或 @RestController 注解,项目默认返回的是一个静态页面,反之,使用了这两个注解之一,返回的就是一个普通的数据。

13、MyBatis 中 # 和 $ 的区别

  • ①、#{变量名} 是预处理的方式,本质是 jdbc 占位符的替换,如果传入字符串,会替代为带有单引号的值,安全性更好
  • ②、${变量名} 是字符串替换,只是对 sql 字符串进行拼接。如果传入字符串,会被替代成字符串的值,不会加单引号
  • #的方式可以防止 sql 注入,相对来说更安全

14、MyBatis 如何一对一、一对多关联

①、一对一的情况:例如 CSDN 的博客一篇博客对应的就是一个作者,这就是一对一的情况

使用 association 标签,用户表和文章表分别在 xml 文件中映射自己的字段,对于此处 一对一的情况,是在文章实体类中存在 user 属性,对于 user 属性的映射采用 association 标签,其中有三个属性:property 指定文章实体类中的属性名、resultMap :指定关联的结果集,基于该结果集组织用户数据,columnPrefix 为了防止两张表同名字段的覆盖,绑定一对一对象

  • 主要代码:
<resultMap id="BaseMap" type="com.example.demo.model.Ariticle">
        <id column="id" property="id"></id>
        <result column="title" property="title"></result>
        <result column="content" property="content"></result>
        <result column="createtime" property="createtime"></result>
        <result column="updatetime" property="updatetime"></result>
        <result column="uid" property="uid"></result>
        <result column="rcount" property="rcount"></result>
        <result column="state" property="state"></result>
        <association property="user"
                     resultMap="com.example.demo.mapper.UserMapper.BaseMap"
                     columnPrefix="u_"></association>
    </resultMap>
    <select id="getArticleById" resultMap="BaseMap">
        select a.*,u.id u_id,u.username u_username,u.password u_password from articleinfo a left join userinfo u on
        a.uid=u.id
        where a.id=#{id}
    </select>
  • 测试结果:

在这里插入图片描述

②、一对多的情况:反过来看,当一篇文章对应一个作者,那么一个作者就可以对应多篇文章,这就是一对多的关系

对于 一对多 的情况使用的是 collection 标签,用户实体类的属性一一进行映射,ariticles 属性需要使用到 collection 对应的关联的结果集是:com.example.demo.mapper.AriticleMapper.BaseMap,使用方式大体和一对一的使用方式一致

  • 主要代码:
<resultMap id="BaseMap" type="com.example.demo.model.User">
        <!-- 主键映射 -->
        <id column="id" property="id"></id>
        <!-- 普通属性映射 -->
        <result column="username" property="username"></result>
        <result column="password" property="password"></result>
        <result column="createtime" property="createtime"></result>
        <result column="updatetime" property="updatetime"></result>
        <result column="state" property="state"></result>
        <collection property="ariticles"
                    resultMap="com.example.demo.mapper.AriticleMapper.BaseMap"
                    columnPrefix="a_">
        </collection>
    </resultMap>
    <select id="getUserAndArticleByUid" resultMap="BaseMap">
        select u.*,a.id a_id,a.title a_title,a.content a_content,
        a.createtime a_createtime,
        a.updatetime a_updatetime from userinfo u left join articleinfo a
        on u.id=a.uid where u.id=#{uid}
    </select>
  • 测试结果:
    在这里插入图片描述

15、SpringBoot 自动配置原理

在这里插入图片描述
SpringBoot 能够自动配置,只需要了解下面几个点:

  • SpringBoot 能够自动配置,主要是因为 @SpringBootApplication 这个注解
  • @SpringBootApplication 的分支 @EnableAutoConfiguration(开启自动导入配置),其里面有一个 @Import(AutoConfigurationPackages.Registrar,class)【自动配置包下所有的Bean定义和注册】注解,它会将需要自动装配的所有的Bean全部装配起来。

问题来了:哪些类是需要自动装配呢?

在这里插入图片描述

如上图,spring factories 里面就配置了(标识了)哪一些需要配置的类


相关文章:

  • Tomcat的安装与优化
  • 第三章 神经网络——什么是神经网路激活函数3层神经网络的简单实现手写数字识别
  • 隧道调频广播覆盖-天线分布式部署是隧道调频广播无线覆盖系统设备介绍
  • 2022-8-31 第七小组 学习日记 (day55)JSP
  • 全球与中国吸油烟机行业市场规模调研及未来前瞻报告2022-2028年
  • 金仓数据库KingbaseES客户端应用参考手册--14. sys_receivewal
  • 软件测试自学不知道如何下手?一篇文章7个步骤带你找准方向
  • 金仓数据库KingbaseES客户端应用参考手册--15. sys_restore
  • map函数传入parseInt函数处理数字输出有误
  • C++ 小游戏 视频及资料集(9)
  • git--查看信息的命令--使用/实例
  • 中秋味的可视化大屏 【以python pyecharts为工具】
  • nodejs+vue+elementui办公用品电商家具网站python
  • 金仓数据库KingbaseES客户端应用参考手册--11. sys_dump
  • 基础知识:英文/计算机/电路/51/汇编
  • 11111111
  • 2017年终总结、随想
  • Angular4 模板式表单用法以及验证
  • angular学习第一篇-----环境搭建
  • - C#编程大幅提高OUTLOOK的邮件搜索能力!
  • css系列之关于字体的事
  • Docker下部署自己的LNMP工作环境
  • Flex布局到底解决了什么问题
  • Java IO学习笔记一
  • MYSQL 的 IF 函数
  • spring cloud gateway 源码解析(4)跨域问题处理
  • SwizzleMethod 黑魔法
  • 从零开始在ubuntu上搭建node开发环境
  • 仿天猫超市收藏抛物线动画工具库
  • 解析带emoji和链接的聊天系统消息
  • 前言-如何学习区块链
  • 强力优化Rancher k8s中国区的使用体验
  • 设计模式 开闭原则
  • 数据可视化之 Sankey 桑基图的实现
  • 微信公众号开发小记——5.python微信红包
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • elasticsearch-head插件安装
  • (3)nginx 配置(nginx.conf)
  • (30)数组元素和与数字和的绝对差
  • (delphi11最新学习资料) Object Pascal 学习笔记---第7章第3节(封装和窗体)
  • (html转换)StringEscapeUtils类的转义与反转义方法
  • (WSI分类)WSI分类文献小综述 2024
  • (附源码)ssm考试题库管理系统 毕业设计 069043
  • (十八)devops持续集成开发——使用docker安装部署jenkins流水线服务
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)Unity3DUnity3D在android下调试
  • (转载)虚函数剖析
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .net 7 上传文件踩坑
  • .net 验证控件和javaScript的冲突问题
  • .NET成年了,然后呢?
  • .NET下ASPX编程的几个小问题
  • .net专家(张羿专栏)
  • @Transactional 详解
  • [].slice.call()将类数组转化为真正的数组