为了账号安全,请及时绑定邮箱和手机立即绑定

Java 最新版并发编程入门与春晚抢红包实战

难度入门
时长 2小时28分
学习人数
综合评分10.00
2人评价 查看评价
10.0 内容实用
10.0 简洁易懂
10.0 逻辑清晰
  • 学习数据

    查看全部
  • spring springMVC  springboot

    1.解释Spring框架中bean的生命周期? 哪些是重要的bean生命周期方法? 你能重载它们吗? https://blog.csdn.net/qq_27184497/article/details/117173285

    Spring Bean的生命周期指的是指Bean从创建到使用最后被销毁的过程,大致可以分为五个过程:实例化前准备阶段  实例化阶段  初始化阶段  使用阶段  销毁阶段

    详细过程如下:

    1.实例化准备阶段:

    第一步:spring通过BeandefinationReader将xml配置文件中的<bean>标签解析成为BeanDefination对象,并加载到容器的集合beanDefinitionMap,

    第二步:扩展点:通过BeanFactoryPostProcessor#postProcessorBeanFacory(DefaultListableBeanFactory facotry)方法进行扩展,比如通过替换数据库连接中的占位符,或者修改beandefination信息等操作   

    2.实例化阶段:通过反射完成实例化

    第三步:然后将容器中的BeanDefination集合通过反射技术通过docreateBean()方法 完成Bean对象实例化;,此时已经实例化了,但还未进行属性赋值;

        如果应用有定义InstantiationAwareBeanPostProcessor(实例化bean感知接口后置处理器)则,在实例化之前后会调用before 和after方法

    postProcessBeforeInstantiation()被调用了----在对象实例化之前调用-----beanName:

    postProcessAfterInstantiation() 被调用了----在对象实例化完成之后调用 ----beanName:

    postProcessPropertyValues():修改属性值方法

    最后然后通过反射完成属性赋值,通过populateBean()完成属性注入

    第四步: 如果bean类实现了多个Aware接口,则按照一定顺序执行这些感知方法,如BeanNameAware  BeanFactoryAware   ApplicatonContextAware 则执行这些方法

    Bean 实现了BeanNameAware  (bean名称的感知接口),则 Spring 调⽤ Bean 的 setBeanName() ⽅法传⼊当前 Bean 的 id 值。

    Bean 实现了 BeanFactoryAware接⼝,则 Spring 调⽤ setBeanFactory() ⽅法传⼊当前spring 顶层容器实例的引⽤。

    Bean 实现了 ApplicationContextAware 接⼝,则 Spring 调⽤ setApplicationContext() ⽅法传⼊当前 ApplicationContext 实例的引⽤

    3.初始化阶段:

    第六步: 通过反射完成初始化

    如果应用中添加了BeanPostProcessor实现类,该类中的before方法和after方法会初始化前后执行

    如果 Bean 实现了 InitializingBean 接⼝,则 Spring 将调⽤ afterPropertiesSet() ⽅法。进行属性再设置

    如果bean在配置⽂件或注解中通过 init-method 属性指定了初始化⽅法(或者@Bean(initMethod ="xxx")),则调⽤该初始化⽅法。

    4.使用阶段:

    从容器中取出bean对象进行使用

    5.销毁阶段:

    第七步:容器关闭后,在bean的作用范围为single 单例,会通过DisposableBean.destroy() 进行bean销毁

    并且在配置⽂件中通过destroy属性指定了销毁⽅法(或者@Bean(destroyMethod ="xxx")),则调⽤该销毁⽅法。

    如果使用bean的作用范围为prototype 原型模式 ,容器直接关闭

    :pring无法进行管理,所以将生命周期交给用户控制,用户用完bean对象后,java垃圾处理器会自动将无用的对象进行回收操作;

    从上可知,spring bean生命周期中有很多接口,我们可以通过实现上面接口来扩展spring:

    BeanFactoryPostProcessor:bean容器后置处理器,

    InstantiationAwareBeanPostProcessor 实例化感知后置处理器,

    各种Aware接口

    BeanPostProcessor 初始化阶段使用 

    InitializingBean 初始化bean  ,调用afterPropertiesSet()

    DisposableBean   销毁bean,调用destroy()

    2. 什么是循环依赖?spring的循环依赖是如何解决的?涉及到哪些类的方法?https://blog.csdn.net/qq_36381855/article/details/79752689

    循环依赖就是两个或者两个以上的 Bean 互相依赖对方。⽐如A依赖于B,B依赖于C,C⼜依赖于A。 应当尽量避免循环依赖发生。

    在spring中,prototype 原型 bean循环依赖(⽆法解决),单例 bean 构造器参数循环依赖(⽆法解决) 会抛出 BeanCurrentlyInCreationException

    spring只能解决单例模式的Bean通过set方法注入属性导致循环依赖。通过三级缓存机制实现,具体流程如下:

    三级缓存分别指:

    singletonObjects:单例池,存放单例对象的ConcurrentHashMap  一级缓存

    earlySingletonObjects :提前暴光的单例对象的Cache 。【用于检测循环引用,与singletonFactories互斥】 二级缓存

    singletonFactories : 单例对象工厂的cache 三级缓存

    Spring 的循环依赖的理论依据基于 Java 的引⽤传递,当获得对象的引⽤时,对象的属性是可以延后设置的,但是构造器必须是在获取引⽤之前;本质是通过提前暴露⼀个ObjectFactory对象 来完成的,

    spring三级缓存实现流程(可以从 getSingleton()方法中掌握这一流程):  

    简单来说ClassA在调⽤构造器完成对象初始化之后,在调⽤ClassA的setClassB⽅法时,发现在从一级缓存singletonObejects查不到,则通过objectFactory将自身提前暴露到Spring容器中,放入三级缓存singletonFactories,然后初始化classB。

    classB在初始化时发现自己需要A对象进行属性注入,当从一级缓存singletonObjects 和 二级缓存earlySingletonObjects中未发现classA,但是在三级缓存singletonFactories中发现classA,

    会将classA放入二级缓存earlySingletonObjects,同时从三级缓存删除; 然后将classA注入到classB对象中;

    classB完成属性填充,执行初始化方法,完成初始化,将自己放入第一级缓存中singletonObjects(此时B是一个完整的对象);

    A得到对象B,将B注入到A中; A完成属性填充,初始化,并放入到一级缓存中。

       

    3.简单概述下Spring IoC容器初始化主体流程? 

    Spring启动过程大致如下:

    1.创建beanFactory,加载配置文件,完成bean配置读取器reader的初始化 (构造器的this()方法)

    2.解析配置文件转化beanDefination,获取到bean的所有属性、依赖及初始化用到的各类处理器等(构造器的scan()方法)

    3.刷新beanFactory容器,初始化所有单例bean

    4.注册所有的单例bean并返回可用的容器,一般为扩展的applicationContext

    代码层面:容器启动时机:创建容器对象,如:

    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");

    该构造方法主要完成三件事

    this() 初始化配置文件reader读取器

    scan() 将bean配置文件的bean信息转换为Beandefination

    refresh() 将Beandefination转换bean对象,保存到容器中,并返回容器

    refresh是核心方法(AbstractApplicationContext#refresh()),当refresh方法执行完毕后,spring应用就完成。该过程比较复杂,关键步骤有:

    1.获取BeanFactory 容器,加载BeanDefition并注册到 BeanDefitionRegistry

    2.获取到容器后,对容器完成准备工作(spirng容器所必须的类注入),以及后置处理工作(空方法需要自己实现)

    3.将注册BeanPostProcessor到容器中,然完成applicationContext所扩展的 国际化初始化  事件分派器初始化 注册监听器等动作

    4.初始化初始化所有剩下的⾮懒加载的单例bean,并添加到单例池中

    5.完成context的刷新。主要是调⽤LifecycleProcessor的onRefresh()⽅法,并且发布事件

     

    4. spring中事务隔离级别?事务传播机制?如何使用事务?

    数据库共定义了四种隔离级别:

    Serializable(串⾏化):可避免脏读、不可重复读、虚读情况的发⽣。(串⾏化) 最⾼

    Repeatable read(可重复读):可避免脏读、不可重复读情况的发⽣。(幻读有可能发⽣) 第⼆该机制下会对要update的⾏进⾏加锁

    Read committed(读已提交):可避免脏读情况发⽣。不可重复读和幻读⼀定会发⽣。 第三

    Read uncommitted(读未提交):最低级别,以上情况均⽆法保证。(读未提交) 最低

    注意:级别依次升⾼,效率依次降低,MySQL的默认隔离级别是:REPEATABLE READ

    事务的传播⾏为

    事务往往在service层进⾏控制,如果出现service层⽅法A调⽤了另外⼀个service层⽅法B,A和B⽅法本身都已经被添加了事务控制,那么A调⽤B的时候,就需要进⾏事务的⼀些协商,这就叫做事务的传播⾏为。

    A调⽤B,我们站在B的⻆度来观察来定义事务的传播⾏为:propagation:( prɒpə'ɡeɪʃin) 传播

    PROPAGATION_REQUIRED

    如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务中,加⼊到这个事务中。这是最常⻅的选择。

    PROPAGATION_REQUIRES_NEW 

    新建事务,如果当前存在事务,把当前事务挂起。

    PROPAGATION_SUPPORTS 

    ⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏。

    PROPAGATION_NOT_SUPPORTED 

    以⾮事务⽅式执⾏操作,如果当前存在事务,就把当前事务挂起。

    PROPAGATION_MANDATORY 

    使⽤当前的事务,如果当前没有事务,就抛出异常。

    PROPAGATION_NEVER 

    以⾮事务⽅式执⾏,如果当前存在事务,则抛出异常。

    PROPAGATION_NESTED

    如果当前存在事务,则在嵌套事务内执⾏。如果当前没有事务,则执⾏与PROPAGATION_REQUIRED类似的操作。

    5. 解释下Spring的事务失效有哪些情况?(重点)https://baijiahao.baidu.com/s?id=1738047834717304748

    1.数据库引擎不支持事务,比如mysql使用的Mysaim。

    2.事务方法没有被public修饰,, 动态代理是通过实现接口或者继承来实现的,所以目标方法必须是public修饰,并且不能是final修饰。

    3.如果事务方法使用final修饰,那么aop就无法在代理类中重写该方法,事务就不会生效

    4.同一个类中,普通方法A嵌套调用事务方法B,在外部调用A()方法时,事务也不会生效,因为这里A()方法中调用的是类本身的方法,而不是代理对象的方法。那么如果确实有这样的需求怎么办呢?引入自身bean,在A方法中嵌套调用bean.B()

    5.使用了错误的事务传播机制,禁止使用事务,也会导致事务失效。如 Never ,Not_Supported都会导致事务失效。

    6.自己try...catch...处理了异常,如果没有异常抛出,则Spring认为程序是正常的,就不会回滚

    7.Spring默认只会回滚RuntimeException和Error对于普通的Exception,不会回滚;如果你想触发其他异常的回滚,需要在注解上配置一下,如:@Transactional(rollbackFor = Exception.class)

    。。。等等

    6.spring事务实现的底层原理?



    7.BeanFactory和ApplicationContext的关系详解? BeanFactory 和 ApplicationContext有什么区别? 

    相同点:两个Spring提供的I0C容器,并且ApplicationContext是BeanFactory的子类,Beanfactory是spring IOC 容器的顶层接口;

    不同点:主要两个区别

    接口提供的功能不同

    BeanFactory:是Spring里面最顶层的接口,只提供了容器的基础功能,比如:读取bean.xml的配置, 添加bean到容器中,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

    ApplicationContext:接口作为BeanFactory的子类,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能,比如: AOP的功能 事件发布/响应机制;   继承MessageSource,因此支持国际化。。。

    实例化时机的区别:

    beanfactory容器: 懒加载Lazy-init,  容器在启动的时候,并不会实例化bean,只有当用户调用getBean()方法的时候才会对配置好的bean进行实例化;

    ApplicationContext容器:当容器启动之后,会主动将bean配置文件中的对象加载容器中,非懒加载模式;

    ApplicationContext容器如何实现懒加载,通过以下的两种方式来延迟实例化:

    1,可以在bean元素配置lazy-init=true来让bean延迟实例化;或者在通过@Bean注解注入对象时,添加@Lazy(true) 注解;

    2,可以在beans元素配置default-lazy-init=true来让这个beans里面所有的bean延迟实例化; 或者在使用springBoot时配置:spring.main.lazy-initialization=true 

    对比BeanFactory延迟实例化和ApplicationContext非延迟实例化优缺点分析

    延迟实例化: 系统启动快,占用资源少;只有使用的时候才会加载对象,但是第一次使用的时候性能较低;

    非延迟实例化: 在系统启动的时候,所有对象被实例化,启动速度慢,启动的时候占用较多系统资源;但由于所有资源都已经被初始化了,所以使用性能高

    ApplicationContext通常的实现有哪些?

    ClassPathXmlApplicationContext:从类的根路径下加载配置⽂件(推荐使⽤): 包含了解析 xml 等⼀系列的内容

    AnnotationConfifigApplicationContext:纯注解模式下启动Spring容器,纯注解模式下启动Spring容器

    FileSystemXmlApplicationContext:从磁盘路径上加载配置⽂件(不推荐使用)

    WebXmlApplicationContext:此容器加载一个XML文件,此文件定义了一个WEB应用的所有bean。


    8.BeanFactory 和FactoryBean 的区别:

    BeanFactory接⼝:是Spring里面最顶层的接口,只提供了容器的基础功能,比如:读取bean.xml的配置, 添加bean到容器中,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。

    FactoryBean 工厂Bean :生产某一类的bean的工厂对象,一般用来创建复杂的bean对象。

    在Spring中Bean分为两种,⼀种是普通Bean, ⼀种是⼯⼚Bean(FactoryBean)

    FactoryBean 工厂接口有三个主要的方法:getObject()  getObjectType() isSingleton()

    public interface FactoryBean<T> {

    // 返回FactoryBean创建的Bean实例,如果isSingleton返回true,则该实例会放到Spring容器的单例对象缓存池中Map

    T getObject() throws Exception;

    // 返回FactoryBean创建的Bean类型

    Class<?> getObjectType();

    // 返回作⽤域是否单例

    default boolean isSingleton() {

    return true;

    }

    }

     

    9.Bean标签能够设置哪些属性?对应BeanDefinetion哪些属性

    在基于xml的IoC配置中,bean标签是最基础的标签。它表示了IoC容器中的⼀个对象。该标签长设置的属性有 id name class scope init-method destory-method lazy-init factory-bean facotry-method

    id属性: ⽤于给bean提供⼀个唯⼀标识。在⼀个标签内部,标识必须唯⼀。

    class属性:⽤于指定创建Bean对象的全限定类名。

    name属性:⽤于给bean提供⼀个或多个名称。多个名称⽤空格分隔。

    scope属性:⽤于指定bean对象的作⽤范围。通常情况下就是singleton。当要⽤到多例模式时,可以配置为prototype。

    lazy-init属性:  用来设置是否=true来让bean延迟实例化

    init-method属性:⽤于指定bean对象的初始化⽅法,此⽅法会在bean对象装配后调⽤。必须是⼀个⽆参⽅法。

    destory-method属性:⽤于指定bean对象的销毁⽅法,此⽅法会在bean对象销毁前执⾏。它只能为scope是singleton时起作⽤。

    factory-bean属性:⽤于指定创建当前bean对象的⼯⼚bean的唯⼀标识。当指定了此属性之后,class属性失效。

    factory-method属性:⽤于指定创建当前bean对象的⼯⼚⽅法,如配合factory-bean属性使⽤,则class属性失效。如配合class属性使⽤,则⽅法必须是static的。

     


    10.解释Spring支持的几种bean的作用域?默认的是什么作用域?单例作用域 和 原型作用域有什么区别?

    在Spring容器中,可以给创建的Bean指定作用范围 通过Bean的scope属性,spring支持以下几种Bean的作用域:

    1.singleton(单例  默认值):spring Ioc  容器中只会存在一个共享的bean 实例,无论有多少个Bean 引用它,始终指向同一个对象。

    2.prototype(每次调用都是新对象):每一次通过spring容器获取prototype 定义的Bean 时,容器都将创建一个新的Bean实例,每个Bean实例都有自己的属性和状态 

    3.request(每次不同的Http请求都会创建一个新对象):在同一次Http请求中,容器会返回该Bean的同一实例。而对不同的Http请求则会产生新的Bean,而且该bean仅在当前Http Request内有效。(不常用)

    4.session(在一次会话中,使用同一个对象):在一次Http Session中,容器会返回该Bean的同一实例。而对不同的Session请求则会创建新的实例,该bean实例仅在当前Session内有效。

    5.globalSession:在一个全局的Http Session中,容器会返回该Bean的同一个实例,仅在使用portlet context时有效。

    默认的Spring bean 的作用域是Singleton; 使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

    单例模式和原型模式的不同点

    单例模式:singleton :对象随着容器的启动被创建,生命周期和容器相同,只要容器在,对象⼀直活着。 当销毁容器时,对象就被销毁了。         

    对象出⽣:当创建容器时,对象就被创建了。

    对象活着:只要容器在,对象⼀直活着。

    对象死亡:当销毁容器时,对象就被销毁了。

    ⼀句话总结:单例模式的bean对象⽣命周期与容器相同。

    原型模式:prototype

    对象出⽣:当对象真正被使用时,也就是 调用getBean()方法时,才会创建新的对象实例。也就是所谓的延迟加载 

    对象活着:只要对象在使⽤中,就⼀直活着。

    对象死亡:当对象⻓时间不⽤时,被java的垃圾回收器回收了。

    ⼀句话总结:多例模式的bean对象,spring框架只负责创建,不负责销毁, 也就是 只能使用 init-method()方法,不能使用destory()方法

     

    11.有哪些不同类型的依赖注入实现方式?(实例化Bean方式有哪些?属性注入的方式有哪些?如何通过注解进行属性注入?有哪些方法)

    7.1 实例化Bean方式有哪些

    1.通过无参数构造器进行注入:

    在默认情况下,它会通过反射调⽤⽆参构造函数来创建对象(如果类中未显示声明无参数构造函数,默认会有一个无参数构造函数)

    2.静态方法注入 :

    使用工厂类的静态方法进行注入,比如,通过classs属性指定工厂类,通过factory-method属性指定  工厂的静态方法

    <!--使⽤静态⽅法创建对象的配置⽅式-->

    <bean id="userService" class="com.lagou.factory.BeanFactory" factory-method="getTransferService"></bean>

    3.实例方法注入:

    与上⾯静态⽅法创建其实类似,区别是⽤于获取对象的⽅法不再是static修饰的了,⽽是类中的⼀个普通⽅法

    7.2 属性注入的方式有哪些?优缺点?

    构造函数注⼊:利⽤带参构造函数实现对类成员的数据赋值 底层通过populateBean()方法完成属性注入

    set⽅法注⼊:通过类的set⽅法实现数据的注⼊(使⽤最多的)Setter方法注入是容器通过调用无参构造器或无参static工厂 方法实例化bean之后,调用该bean的setter方法,即实现了基于setter的依赖注入


    12.@Autowired @Resoure有什么区别?  

    @Autowired(推荐使⽤)

    @Autowired为Spring提供的注解,采取的策略为按照class类型 by class 注⼊ 对应Bean的class属性 ,需要导⼊包org.springframework.beans.factory.annotation.Autowired

    当⼀个类型有多个bean值的时候,会造成⽆法选择具体注⼊哪⼀个的情况,如何解决?使用@Qualififier注解@Qualifier(name="xxx") 

    @Resource

    @Resource 注解由 J2EE 提供,默认按照 名称 by name ⾃动注⼊ 需要导⼊包 javax.annotation.Resource。例如:

    1.@Resource 

    private AccountDao accountDao;

    2.@Resource(name="studentDao") 

    private StudentDao studentDao;

    3.@Resource(type="TeacherDao") 

    private TeacherDao teacherDao;

    4.@Resource(name="manDao",type="ManDao") 

    private ManDao manDao;

    如果同时指定了 name 和 type,则从Spring上下⽂中找到唯⼀匹配的bean进⾏装配,找不到则抛出异常。

    如果指定了 name,则从上下⽂中查找名称(id)匹配的bean进⾏装配,找不到则抛出异常。

    如果指定了 type,则从上下⽂中找到类似匹配的唯⼀bean进⾏装配,找不到或是找到多个,都会抛出异常。

    如果既没有指定name,⼜没有指定type,则⾃动按照byName⽅式进⾏装配;

     

    13.分析下BeanFactoryPostProcessor 和BeanPostProcessor 的作用以及应用场景

    使用时机:

    在BeanFactory初始化之后可以使⽤BeanFactoryPostProcessor进⾏后置处理做⼀些事情

    在Bean对象实例化(并不是Bean的整个⽣命周期完成)之后可以使⽤BeanPostProcessor进⾏后置处理做⼀些事情

    详细分析如下:

    1.BeanPostProcessor是针对Bean级别的处理,可以针对某个具体的Bean,核心方法有两个 

    postProcessorBeforeInitlization(Object bean, String beanName)     postProcessorAfterInitalization(Object bean, String beanName)方法该接⼝提供了两个⽅法,

    分别在Bean的初始化⽅法前和初始化⽅法后执⾏,具体这个初始化⽅法指的是什么⽅法,类似我们在定义bean时,定义了init-method所指定的⽅法

    BeanPostProcessor,默认是会对整个Spring容器中所有的bean进⾏处理。如果要对具体的某个bean处理,可以通过⽅法参数判断,两个类型参数分别为Object和String

    2.BeanFactoryPostProcessor:是对整个spring 容器BeanFactory级别的处理,是针对整个Bean的⼯⼚进⾏处理,典型应⽤:PropertyPlaceholderConfifigurer,替换占位符

    提供了一个方法postProcessBeanFactory(ConfigurableListableBeanFactory config)   

    我们可以根据该方法传入的参数 ConfigurableListableBeanFactory   中获取到 getBeandefination(),然后对BeanDeFination中的属性进行修改

    (BeanDeFination对应着xml中的<Bean> 标签, Spring 解析 bean 标签成为⼀个 JavaBean,这个JavaBean 就是 BeanDefifinition)

    注意:调⽤ BeanFactoryPostProcessor ⽅法时,这时候bean还没有实例化,此时 bean 刚被解析成BeanDefifinition对象

    补充:通过什么接口可以获取spring的容器:ApplicationContextAware(需要Bean类实现该感知接口ApplicationContextAware,通过该接口的setApplicationContext()方法获取容器)

     


    14. 说下spring的动态代理:

    JDK动态代理(默认的代理方式)

    JDK动态代理是使用 java.lang.reflect 包下的代理类来实现. JDK动态代理动态代理必须要有接口.

    GLIB动态代理

    JDK动态代理必须要有接口, 但如果要代理一个没有接口的类该怎么办呢? 

    这时我们可以使用CGLIB动态代理. CGLIB动态代理的原理是生成目标类的子类, 这个子类对象就是代理对象, 代理对象是被增强过的.


     

     

     

     



    如何给Spring 容器提供配置元数据?Spring有几种配置方式

    Spring配置文件包含了哪些信息

    Spring基于xml注入bean的几种方式


    Spring框架中的单例bean是线程安全的吗?

    Spring如何处理线程并发问题?


    什么是Spring的内部bean?什么是Spring inner beans?

    在 Spring中如何注入一个java集合?

    什么是bean装配?

    什么是bean的自动装配?

    解释不同方式的自动装配,spring 自动装配 bean 有哪些方式?

    使用@Autowired注解自动装配的过程是怎样的?

    自动装配有哪些局限性?

    你可以在Spring中注入一个null 和一个空字符串吗?




    Spring数据访问(14)

    解释对象/关系映射集成模块

    在Spring框架中如何更有效地使用JDBC?

    解释JDBC抽象和DAO模块

    spring DAO 有什么用?

    spring JDBC API 中存在哪些类?

    JdbcTemplate是什么

    使用Spring通过什么方式访问Hibernate?使用 Spring 访问 Hibernate 的方法有哪些?

    如何通过HibernateDaoSupport将Spring和Hibernate结合起来?

    Spring支持的事务管理类型, spring 事务实现方式有哪些?

    Spring事务的实现方式和实现原理

    说一下Spring的事务传播行为

    说一下 spring 的事务隔离?

    Spring框架的事务管理有哪些优点?

    你更倾向用那种事务管理类型?

    Spring面向切面编程(AOP)(13)


    什么是AOP

    Spring AOP and AspectJ AOP 有什么区别?AOP 有哪些实现方式?

    JDK动态代理和CGLIB动态代理的区别

    如何理解 Spring 中的代理?

    解释一下Spring AOP里面的几个名词

    Spring在运行时通知对象

    Spring只支持方法级别的连接点

    在Spring AOP 中,关注点和横切关注的区别是什么?在 spring aop 中 concern 和 cross-cutting concern 的不同之处

    Spring通知有哪些类型?

    什么是切面 Aspect?

    解释基于XML Schema方式的切面实现

    解释基于注解的切面实现

    有几种不同类型的自动代理?

     

    查看全部
    0 采集 收起 来源:课程介绍

    笔记审核中笔记正在审核中,仅自己可见 2023-01-12

  • Mysql

    1.一条SQL查询语句是如何执行的?mysql的服务架构

    整体结构:MySQL Server架构自顶向下大致可以分网络连接层、服务层、存储引擎层和系统文件层。

    网络连接层:也就是各种数据库连接客户端,如 常见的 Java、C、Python、.NET等,它们通过各自API技术与MySQL建立连接。

    服务层:是MySQL Server的核心,主要包含 连接器、查询缓存、分析器、优化器、执行器等。

    存储引擎层:各种存储引擎,其架构模式是插件式的,存储引擎层负责数据的存储和提取,MySQL 5.5.5 版本InnoDB开始成为了默认存储引擎。

    系统文件层:各种系统文件,比如日志文件,比如 binlog  undo.log redo.log 

    上面我们提到mysql的服务层划分:

    第一步:连接器:负责管理链接,用户账户权限验证   

    第二步:查询缓存:查询缓存,命中直接返回 

    连接建立完成后,你就可以执行sql查询语句了,执行逻辑就会来到第二步:查询缓存。

    MySQL 拿到一个查询请求后,会先到查询缓存看看,之前是不是执行过这条语句。 

    如果查询命中缓存,MySQL 不需要执行后面的复杂操作,就可以直接返回结果,这个效率会很高。

    但是:不建议使用查询缓存,原因如下:

    因为查询缓存往往弊大于利。查询缓存的失效非常频繁,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。

    对于你确定要使用查询缓存的语句,可以用 SQL_CACHE 显式指定,像下面这个语句一样:

    mysql> select SQL_CACHE * from T where ID=10;

    同时:需要注意的是,MySQL 8.0 版本直接将查询缓存的整块功能删掉了,也就是说 8.0 开始彻底没有这个功能了。

    查询缓存如何设置:

    have_query_cache   表示当前mysql版本是否支持查询缓存。

    query_cache_type 0 时表示关闭,1时表示打开,2表示只要select 中明确指定SQL_CACHE才缓存。 

    query_cache_limit  表示单个结果集所被允许缓存的最大值。 

    query_cache_min_res_unit 每个被缓存的结果集要占用的最小内存。 

    query_cache_size   用于查询缓存的内存大小。

    qcache_free_memory   查询缓存目前剩余空间大小。

    qcache_hits      查询缓存的命中次数。 

    qcache_inserts      查询缓存插入的次数。

    第三步:分析器:词法分析 语法分析

    如果没有命中查询缓存,就要开始真正执行语句了。首先,MySQL 需要知道你要做什么,因此需要对 SQL 语句做解析,生成"解析树"。

    预处理器根据一些MySQL规则进一步检查“解析树”是否合法;

    分析器先会做“词法分析”。你输入的是由多个字符串和空格组成的一条 SQL 语句,MySQL 需要识别出里面的字符串分别是什么,代表什么。

    做完了这些识别以后,就要做“语法分析”。根据词法分析的结果,语法分析器会根据语法规则,判断你输入的这个sql语句是否满足 MySQL 语法。

     

    第四步:执行优化器

    经过了分析器,MySQL 就知道你要做什么了。在开始执行之前,还要先经过优化器的处理。

    优化器是 决定使用哪个索引; 决定各个表的连接顺序。 

    原则是:尽可能扫描少的数据库行纪录

    第五步:执行器: 操作存储引擎,获取结果并返回

    MySQL 通过分析器知道了你要做什么,通过优化器知道了该怎么做,于是就进入了执行器阶段,开始执行语句。

    开始执行的时候,先检验权限

    如果操作表的权限,就打开表继续执行。打开表的时候,执行器就会根据表的存储引擎,去使用这个引擎提供的接口。

     

    2.日志系统:一条SQL更新语句是如何执行的? 

    关键词: WAL write ahead logging 先写日志、 undolog redolog 、 binlog、 两阶段提交  innodb存储引擎

    如果要将 ID=2 这一行的值加 1,SQL 语句就会这么写:

    mysql> update T set c=c+1 where ID=2;

    该过程是如何执行的呢?

    首先,可以确定的说,查询语句的那一套流程,更新语句也是同样会走一遍。

    1.你执行语句前要先连接数据库,这是连接器的工作。

    2.前面我们说过,在一个表上有更新的时候,跟这个表有关的查询缓存会失效,所以这条语句就会把表 T 上所有缓存结果都清空。这也就是我们一般不建议使用查询缓存的原因。

    3.接下来,分析器会通过词法解析和语法解析知道这是一条更新语句。

    4.优化器决定要使用 ID 这个索引。

    5.然后,执行器负责具体执行,找到这一行,然后更新。

    更新流程还涉及两个重要的日志模块:redo log(重做日志)和 binlog(归档日志)

    客户端执行DDL语句(create)/ DML语句(insert,update,delete)/DCL语句(grant,revoke),数据库服务端执行的时候会涉及到 undolog(撤销日志) redolog(重做日志) 和 binlog (归档日志)

    show engines:查看当前数据库支持的引擎信息,在5.5版本之前默认采用MyISAM存储引擎,从5.5开始采用InnoDB存储引

    常见存储引擎有:

    InnoDB:支持事务,具有提交,回滚和崩溃恢复能力,事务安全

    MyISAM:不支持事务和外键,访问速度快

    Memory:利用内存创建表,访问速度非常快,因为数据在内存,而且默认使用Hash索引,但是一旦关闭,数据就会丢失

    了解 redolog(重做日志) 和 binlog (归档日志) 之前我们先详细学习下Innodb存储引擎的架构模型

      分析下 Innodb 存储引擎:

    Innodb存储引擎: 它支持外键,擅长处理事务,具有自动崩溃恢复的特性;

    InnoDB引擎架构图:主要分为内存结构和磁盘结构两大部分

    内存结构:

    内存结构主要包括Buffer Pool、Change Buffer、Adaptive Hash Index和Log Buffer四大组件

    Buffer Pool:缓冲池,以缓存页page作为基本单位,大小默认为16k, 缓存页之间通过链表进行链接

    作用:在InnoDB访问表记录和索引时会在Page页中缓存,以后使用可以减少磁盘IO操作,提升效率

    page页的类型分为三种:

    free page :空闲page,未被使用

    clean page:被使用page,数据没有被修改过

    dirty page:脏页,被使用page,数据被修改过,页中数据和磁盘的数据产生了不一致

    page页通过三种链表来进行维护

    free list :表示空闲缓冲区,管理free page

    flush list:表示需要刷新到磁盘的缓冲区,管理dirty page,内部page按修改时间排序。脏页即存在于flush链表,也在LRU链表中,但是两种互不影响,LRU链表负责管理page的可用性和释放,而flush链表负责管理脏页的刷盘操作。

    lru list  :表示正在使用的缓冲区,管理clean page和dirty page,缓冲区以midpoint为基点,前面链表称为new列表区,存放经常访问的数据,占63%;后面的链表称为old列表区,存放使用较少数据,占37%。

    Change Buffer:

    写缓存区:在进行DML操作时,如果缓存池中没有其相应的Page数据,并不会立刻将磁盘页加载到缓冲池,而是在写缓冲区记录缓冲变更,等未来数据被读取时,再将数据合并恢复到BP中。

    当更新一条记录时,该记录在BufferPool存在,直接在BufferPool修改,一次内存操作。

    如果该记录在BufferPool不存在(没有命中),会直接在ChangeBuffer进行一次内存操作,不用再去磁盘查询数据,避免一次磁盘IO。

    当下次查询记录时,会先进性磁盘读取,然后再从ChangeBuffer中读取信息合并,最终载入BufferPool中。

     

    Adaptive Hash Index:自适应哈希索引,用于优化对BP数据的查询

    InnoDB存储引擎会自动根据访问的频率和模式来为某些页建立哈希索引。

    Log Buffer: 日志缓冲区,用来保存要写入磁盘上log文件(Redo/Undo)的数据

         LogBuffer主要是用于记录InnoDB引擎日志,在DML操作时会产生Redo和Undo日志

    磁盘结构:

    表空间:存放表结构和数据(InnoDB使用聚集索引(聚簇索引),索引和记录在一起存储,既缓存索引,也缓存记录。)

    数组字典:InnoDB数据字典由内部系统表组成,这些表包含用于查找表、索引和表字段等对象的元数据

    redo日志:用于在崩溃恢复期间更正不完整事务写入的数据。MySQL以循环方式写入重做日志文件,记录InnoDB中所有对Buffer Pool修改的日志。

    undo日志:撤消日志是在事务开始之前保存的被修改数据的备份,用于例外情况时回滚事务

    3.详细解析日志文件 undo.log redo.log binlog作用和功能?

    undo.log撤销日志:

    Undo Log:数据库事务开始之前,会将要修改的记录存放到 Undo 日志里,当事务回滚时或者数据库崩溃时,可以利用 Undo 日志,撤销未提交事务对数据库产生的影响

    Undo Log产生和销毁:

    Undo Log在事务开始前产生;

    事务在提交时,并不会立刻删除undolog,innodb会将该事务对应的undo log放入到删除列表中,后面会通过后台线程purge thread进行回收处理。

    Undo Log属于逻辑日志,记录一个变化过程。例如执行一个delete,undolog会记录一个insert;执行一个update,undolog会记录一个相反的update。

    Undo Log存储:undo log采用段的方式管理和记录。在innodb数据文件中包含一种rollback segment回滚段,内部包含1024个undo log segment。

    Undo Log作用:

    实现事务原子性:事务处理过程中,如果出现了错误或者用户执行了 ROLLBACK 语句,MySQL 可以利用 Undo Log 中的备份将数据恢复到事务开始之前的状态

    实现MVCC多版本控制:事务未提交之前,Undo Log保存了未提交之前的版本数据,Undo Log 中的数据可作为数据旧版本快照供其他并发事务进行快照读

    redo.log重做日志:

    redo.log:以恢复操作为目的,在数据库发生意外时重现操作。防止在发生故障的时间点,尚有脏页未写入表的 IBD 文件中,在重启 MySQL服务的时候,根据Redolog进行重做,从而达到事务的未入磁盘数据进行持久化这一特性。

         redo.Log 的生成和释放: 

    随着事务操作的执行,就会生成Redo Log,在事务提交时会将产生Redo Log写入Log Buffer,并不是随着事务的提交就立刻写入磁盘文件。(两阶段提交)

    等事务操作的脏页写入到磁盘之后,Redo Log 的使命也就完成了,Redo Log占用的空间就可以重用(被覆盖写入)。

    redo.log的写入机制:循环顺序写

    属于innodb,4个文件共4GB大小,环形,磁盘地址有序,负责事务。crash-safe能力。 

    Mysql数据库的写前日志(Write Ahead Log, WAL)机制,也就是说,在实际将数据记录表中之前,先把修改的数据记到日志文件中,以便故障时进行恢复。

    binlog归档日志:

    属于mysql server层,无大小限制,会无限创建binlog文件,负责归档恢复。

    binlog是记录所有数据库表结构变更以及表数据修改的二进制日志,不会记录SELECT和SHOW这类操作。

    binlog日志是以事件形式(Log Event)记录,还包含语句所执行的消耗时间。

    应用场景:

    主从复制:在主库中开启Binlog功能,这样主库就可以把Binlog传递给从库,从库拿到Binlog后实现数据恢复达到主从数据一致性。

    数据恢复:通过mysqlbinlog工具来恢复数据。

    redo 和undo 两种日志的相同和差异:

    1redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。

    2redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。

    3redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。

    4.数据库的两阶段提交:为了保证binlog和redolog两个日志之间的一致性

    两阶段提交过程:

    1.server拿到数据对数据修改,把修改结果发给引擎。 

    2.引擎记录redo log状态为 prepare,之后告诉server可以提交事务。 

    3.server接到通知后记录binlog,之后调引擎接口提交事务。 

    4.引擎接到提交事务的通知,将redo log改为commit状态。

    引擎修改两次redo log的状态的操作叫两阶段提交。 

    为什么日志需要“两阶段提交”。这里不妨用反证法来进行解释。  崩溃恢复  和  数据主从同步

    由于 redo log 和 binlog 是两个独立的逻辑,如果不用两阶段提交,要么就是先写完 redo log 再写 binlog,或者采用反过来的顺序。我们看看这两种方式会有什么问题

    仍然用前面的 update 语句来做例子。假设当前 ID=2 的行,字段 c 的值是 0,再假设执行 update 语句过程中c=1 ,在写完第一个日志后,第二个日志还没有写完期间发生了 crash,会出现什么情况呢?

    第一种情况:先写 redo log 后写 binlog。假设在 redo log 写完,binlog 还没有写完的时候,MySQL 进程异常重启。

       由于我们前面说过的,redo log 写完之后,系统即使崩溃,仍然能够把数据恢复回来,所以恢复后这一行 c 的值是 1。

               但是由于 binlog 没写完就 crash 了,这时候 binlog 里面就没有记录这个语句。因此,之后备份日志的时候,存起来的 binlog 里面就没有这条语句。

               然后你会发现,如果需要用这个 binlog 来恢复临时库的话,由于这个语句的 binlog 丢失,这个临时库就会少了这一次更新,恢复出来的这一行 c 的值就是 0,与原库的值不同。

    第二种情况:先写 binlog 后写 redo log。如果在 binlog 写完之后 crash,由于 redo log 还没写,崩溃恢复以后这个事务无效,所以这一行 c 的值是 0。

       但是 binlog 里面已经记录了"把 c 从 0 改成 1"这个日志。所以,在之后用 binlog 来恢复的时候就多了一个事务出来,恢复出来的这一行 c 的值就是 1,与原库的值不同。

    可以看到,如果不使用“两阶段提交”,那么数据库的状态就有可能和用它的日志恢复出来的库的状态不一致。


    5.详解事务的ACID特性以及实现原理  和 事务隔离级别 以及 多版本控制MVCC

    事务:简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败。在 MySQL 中,事务支持是在引擎层实现的;

    提到事务,你肯定会想到 ACID,那它们是如何实现的呢?(只有Innodb存储引擎支持事务)

    原子性(Atomicity): 事务是一个原子操作单元,其对数据的修改,要么全都执行,要么全都不执行。

    事务的原子性是通过WAL(先写日志再刷磁盘)实现。最终落地到 undo日志实现

    隔离性(Isolation):指的是一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对其他的并发事务是隔离的。

    InnoDB 支持的隔离性有 4 种,隔离性从低到高分别为:读未提交、读已提交、可重复读、串行化。 

    底层是通过对 锁 和 多版本控制(MVCC)技术的封装,从而实现不通的隔离级别

    持久性:指的是一个事务一旦提交,它对数据库中数据的改变就应该是永久性的,后续的操作或故障不应该对其有任何影响,不会丢失。

    redo log在系统宕机重启之类的情况时,可以修复数据,从而保障事务的持久性。

    MySQL的持久性也与WAL技术相关,redo log在系统Crash重启之类的情况时,可以修复数据,从而保障事务的持久性。

    通过原子性可以保证逻辑上的持久性,通过存储引擎的数据刷盘可以保证物理上的持久性。

    一致性(Consistency): 指的是事务开始之前和事务结束之后,数据库的完整性限制未被破坏。一致性包括两方面的内容,分别是约束一致性和数据一致性。

    一致性也可以理解为数据的完整性。数据的完整性是通过原子性、隔离性、持久性来保证的,而这3个特性又是通过 Redo/Undo 来保证的

     

    6.如果没有事务隔离会产生什么问题?

    当数据库上有多个事务同时操作同一条记录的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题

    更新丢失

    当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题:最后的更新覆盖了由其他事务所做的更新。

    当两个或多个事务更新同一行记录,会产生更新丢失现象。可以分为回滚覆盖和提交覆盖。

    回滚覆盖:一个事务回滚操作,把其他事务已提交的数据给覆盖了。

    提交覆盖:一个事务提交操作,把其他事务已提交的数据给覆盖了。

    脏读: 读到其他事务未提交的数据;   (一个事务读取了其它事务update后未提交的数据,注意是针对其它事务的修改未提交的操作 )

    不可重复读: 前后读取的同一条记录内容不一致; (一个事务读取了其它事务update或delete后已提交的数据,注意是针对其它事务修改或删除已提交的操作 )

    幻读: 前后读取的记录数量不一致。   (一个事务读取了其它事务新insert已经提交的数据。注意是针对其它事务新增已提交的数据)

    7.mysql的隔离级别有哪些? 隔离级别是如何实现的?隔离级别如何设置?

    在谈隔离级别之前,你首先要知道,隔离得越严实,数据库执行效率就会越低。因此很多时候,我们都要在二者之间寻找一个平衡点。

    SQL标准的事务隔离级别包括:

    读未提交 : 一个事务还没提交时,它做的变更就能被别的事务看到。(别人改数据的事务尚未提交,我在我的事务中也能读到)

    读已提交 : 一个事务提交之后,它做的变更才会被其他事务看到。  (别人改数据的事务已经提交,我在我的事务中才能读到)

    可重复读**: 一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。(别人改数据的事务已经提交了,但该事务比我的事务开始的晚,我在我的事务中也不去读)

    串行化: 顾名思义是对于同一行记录,当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行;(我的事务尚未提交,别人就别想改数据)

    数据库对四种隔离级别的实现,是依靠视图来实现的:

    1、读未提交:没有视图的概念,直接返回记录的最新值; 

    2、读已提交:每次执行sql语句之前创建视图; 

    3、可重复读:每次创建事务的时候创建视图; 

    4、串行化:通过加锁来避免并行访问

    事务设置隔离级别的相关命令:

    mysql>  show variables like 'transaction_isolation';查看事务隔离级别

    set tx_isolation='各个隔离级别';// 设置事务隔离级别



    8.什么是索引?以及索引的作用?常用的索引有哪些类型?

    索引一种数据结构,通常采用B+树实现;为了提升查询速率;类似于新华字典中的字典目录,索引是存储在文件中的,因此需要占据物理空间。因此并不是索引越多越好

    索引可以分为以下几种:

    从应用层次划分:普通索引:没有任何限制、  唯一索引:唯一可为空、 主键索引:唯一且非空、 复合索引:遵循最左前缀原则

    从数据存储和索引键值逻辑关系划分:非聚集索引(非聚簇索引,数据和索引分开存放 ,比如mysame)、聚集索引(聚簇索引 比如 innodb引擎将数据和索引存储在一个表中)

    从索引键值类型划分:主键索引、辅助索引(二级索引)

    从索引存储数据结构划分:B+树索引、Hash索引、FULLTEXT全文索引等

    9.索引采用了哪些数据结构?常用的有三种:哈希表  有序数组   B+树 

    哈希表:

    哈希表是一种键值存储数据的结构,我们只要输入待查找的键即 key,就可以找到其对应的值即 Value。

    如果产生哈希冲突,使用拉链法接入,在节点下面挂一个数组或链表

    查找一个元素的过程:

    处理步骤就是:首先,将 key值 通过哈希函数算出 N;如果存在链表,按顺序遍历,找到目标记录。

    哈希表只适用于等值查询,其区间查询效率很低;

    该种数据结构主要应用在 Memory 存储引擎 和 innodb存储引擎的自适应hash索引中

    有序数组:

    基于排序的数组实现;

    有序数组能够解决hash函数不能满足支持快速范围查询。查找速度为log(n),但缺陷也很明显,针对插入和删除场景,需要挪动后面的整个记录,代价太高。

    可以结合二分法就可以快速得到某个等值查询,这个时间复杂度是 O(log(N))。

    有序数组适用于静态搜索引擎。

    B+树(为什么非要采用B+树)

    为什么数据库存储使用b+树 而不是二叉树,因为二叉树树高过高,每次查询都需要访问过多节点,即访问数据块过多,而从磁盘随机读取数据块过于耗时。

    MySQL的存储结构

    表存储结构

    单位:表>段>区>页>行

    在数据库中, 不论读一行,还是读多行,都是将这些行所在的页进行加载。也就是说存储空间的基本单位是页。

    一个页就是一棵树B+树的节点,数据库I/O操作的最小单位是页,与数据库相关的内容都会存储在页的结构里


    10.数据库三大范式

    第一范式:每一列都是不可以再才拆分的

    第二范式:基于第一范式,非主键字段完全依赖主键字段,而不是依赖于主键字段的一部分

    第三范式:在第二范式的基础上,非主键列只依赖于主键,不依赖于其他非主键。

    在设计数据库结构的时候,要尽量遵守三范式,如果不遵守,必须有足够的理由。比如性能。事实上我们经常会为了性能而妥协数据库的设计


    11.binlog的应用场景? Binlog 日志文件的录入方式,录入方式优缺点对比?

    Binlog是记录所有数据库表结构变更以及表数据修改的二进制日志,不会记录SELECT和SHOW这类操作。

    Binlog日志是以事件形式(Log Event)记录,还包含语句所执行的消耗时间。开启Binlog日志有以下两个最重要的使用场景。

    主从复制:在主库中开启Binlog功能,这样主库就可以把Binlog传递给从库,从库拿到Binlog后实现数据恢复达到主从数据一致性。

    数据恢复:通过mysqlbinlog工具来恢复数据。

    binlog的日志文件录入方式有三种: row statment  mixed 

    ROW(row-based replication, RBR):日志中会记录每一行数据被修改的情况,  修改记录复制

    优点:能清楚记录每一个行数据的修改细节,能完全实现主从数据同步和数据的恢复。

    缺点:批量操作,会产生大量的日志,或者修改表结构,尤其是alter table会让日志暴涨。

    STATMENT(statement-based replication, SBR):记录每一条修改数据的SQL语句到master的Binlog中,简称SQL语句复制。

    优点:日志量小,减少磁盘IO,提升存储和恢复速度

    缺点:在某些情况下会导致主从数据不一致,比如last_insert_id()、now()等函数。 无法记录数据库函数

    MIXED(mixed-based replication, MBR):以上两种模式的混合使用,一般会使用STATEMENT模式保存binlog,对于STATEMENT模式无法复制的操作使用ROW模式保存binlog,MySQL会根据执行的SQL语句选择写入模式。


    12.分析下mysql的存储引擎有哪些?优缺点?以及Innodb和mysiam的区别?存储引擎选择?

    通过 show engines 可以查看当前数据库所支持的引擎信息;主要应用的是 innodb myisam , 在mysql版本5.5默认采用myisam存储引擎,5.6.版本之后采用innodb存储引擎

    innodb 和 myisam 的区别

    锁机制

    InnoDB默认支持行级锁,锁定指定记录。基于索引来加锁实现。

    MyISAM默认支持表级锁,锁定整张表。

    索引结构

    InnoDB使用聚集索引(聚簇索引),索引和记录在一起存储,既缓存索引,也缓存记录。

    MyISAM使用非聚集索引(非聚簇索引),索引和记录分开。

    并发处理能力

    MyISAM使用表锁,会导致写操作并发率低,读之间并不阻塞,读写阻塞。

    InnoDB读写阻塞可以与隔离级别有关,可以采用多版本并发控制(MVCC)来支持高并发

    存储文件

    InnoDB表对应两个文件,一个.frm表结构文件,一个.ibd数据文件。InnoDB表最大支持64TB;

    MyISAM表对应三个文件,一个.frm表结构文件,一个MYD表数据文件,一个.MYI索引文件。从MySQL5.0开始默认限制是256TB。

    如何选择存储引擎:

    如果没有特别的需求,使用默认的Innodb即可。

    MyISAM:以读写插入为主的应用程序,比如博客系统、新闻门户网站。

    Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。


    13.详解索引为什么是B+tree数据结构,而不采用B tree

    B+树索引:首先B+树具备以下特性:

    1.非叶子节点不存储data数据,只存储索引值,这样便于存储更多的索引值

    2.叶子节点包含了所有的索引值和data数据,也就是说 一颗树就是带有索引的完整表记录

    3.叶子节点用指针连接,提高区间的访问性能

    相比B树,B+树进行范围查找时,只需要查找定位两个节点的索引值,然后利用叶子节点的指针进行遍历即可。而B树需要遍历范围内所有的节点和数据,显然B+Tree效率高。


    14.什么情况下会导致索引失效? 查询范围不明确导致索引失效

    1. 使用like 左边包含 %

    2. 不满足最左前缀原则

    3. 在索引列上进行计算或者使用函数

    4. 使用了select * ,会导致全表查询

    5. 查询时字段类型不同,如  某个表中的code 字段 varchar类型 添加了索引  查询时 code='101'可以命中索引,但是code=101就会导致全表扫描status

    6. 错误的使用 or关键字,注意:如果使用了or关键字,那么它前面和后面的字段都要加索引,不然所有的索引都会失效,例如:where id=1 or service_code ='test02' or  status =1; 前两个字段有索引,但是status没有索引

    7. 使用 order by 关键字容易导致索引失效

    如果order by语句中没有加where或limit关键字,该sql语句将不会走索引。

    对不同的索引做order by

    联合索引不满足最左匹配原则

    如果order by后面有一个联合索引的多个字段,它们具有不同的排序规则也会导致索引失效

    8. 使用表中的列进行对比

    9. 错误的使用 not in 或者 not exists


    15.索引分析和优化?如何对数据库sql语句进行索引分析与优化?explain 命令是如何执行的?

    我们可以通过explian命令来查询sql语句的执行情况,例如 explian select 语句

    explian命令有一些关键信息 如 select_type  type  possiable_key key  rows  extra 

    详细解析

    select_type 表示sql语句查询类型: 例如,SIMPLE : 表示查询语句不包含子查询或union;SUBQUERY:SELECT子查询语句

    type: 表示存储引擎查询数据时采用的方式,通过它可以判断出查询是全表扫描还是基于索引的部分扫描。

    all: 全表查询

    index:表示基于索引的全表扫描,先扫描索引再扫描全表数据。

    range:表示使用索引范围查询。使用>、>=、<、<=、in等等。

    ref:表示使用非唯一索引进行单值查询。

    eq_ref:一般情况下出现在多表join查询,表示前面表的每一个记录,都只能匹配后面表的一行结果。

    const:表示使用主键或唯一索引做等值查询,常量查询。

    NULL:表示不用访问表,速度最快。

    possible_key:可能会使用到的索引

    key:命中的索引,使用的索引

    rows:估算SQL要查询到结果需要扫描多少行记录。这个值越小越好

    extra:查询时的扩展信息

    Using where :表示查询需要通过索引回表查询数据。

    Using index:表示查询需要通过索引,索引就可以满足所需数据。

    Using filesort:表示查询出来的结果需要额外排序,数据量小在内存,大的话在磁盘,因此有Using filesort建议优化。

    Using temprorary:查询使用到了临时表,一般出现于去重、分组等操作。


    16.什么是回表查询(重点)?

    回表查询:InnoDB索引有聚簇索引和辅助索引。聚簇索引的叶子节点存储行记录,InnoDB必须要有,且只有一个。

    辅助索引的叶子节点存储的是主键值和索引字段值,通过辅助索引无法直接定位行记录,通常情况下,需要扫码两遍索引树。

    先通过辅助索引定位主键值,然后再通过聚簇索引定位行记录,这就叫做回表查询,它的性能比扫一遍索引树低。

    总结:通过索引查询主键值,然后再去聚簇索引查询记录信息


    17.Mysql慢查询实战+SQL优化,慢查询日志分析?如何查看慢查询日志?如何优化慢查询日志

    注意点:切记在生产环境上开启慢查询,会影响数据库性能 variables slow_query_log

    1.首先查看慢查询是否开启: SHOW VARIABLES LIKE 'slow_query_log%'

    2.开启慢查询并设置慢查询: 如果为OFF,则通过命令 SET global slow_query_log = ON 开启 并设置 long_query_time = 10 指定慢查询的阈值

    3.查看慢查询sql文件:MySQL 提供了一个慢查询日志分析工具mysqldumpslow,可以通过该工具分析慢查询日志内容,主要看执行时长,锁表时间和sql语句等关键字段

    4.通过explian命令分析慢查询sql

    5.进行sql语句优化,并监控性能

     

    18.事务并发控制的实现方案有哪些? 说下 MVCC 以及 mysql的锁机制    ****

    事务并发会导致更新丢失,脏读,不可重复读,幻读问题,为了避免上述问题,我们可以通过以下方案解决:

    1.通过排队的方法:就是完全顺序执行所有事务的数据库操作,不需要加锁,简单的说就是全局排队;

    2.排他锁:如果事务之间涉及到相同的数据项时,会使用排他锁,先进入的事务独占数据项以后,其他事务被阻塞,等待前面的事务释放锁。

    3.读写锁:读写锁区分读操作和写操作,让读和读之间不加锁,但是读写或写读或写写需要加锁

    4.通过MVCC机制实现读读,读写,写读不加锁,通过读取副本(实现读写不加锁操作),但是写写仍然需要加锁;

    所谓的MVCC是什么呢?多版本控制,基于 Copy on Write机制实现(先写日志再刷入磁盘)

    在事务A开始写操作的时候会copy一个记录的副本(记录在undo.log日志中),其他事务B读操作会读取这个记录副本,因此不会影响其他事务对此记录的读取,实现写和读并行。

    MVCC实现原理:

    MVCC的实现原理主要依赖于记录中的三个隐藏字段,undolog, read view来实现的。

    在 MVCC 并发控制中,读操作可以分为两类: 快照读(Snapshot Read)与当前读 (Current Read)。

    快照读:读取的是记录的快照版本(有可能是历史版本),不用加锁。(select)

    当前读:读取的是记录的最新版本,并且当前读返回的记录,都会加锁,保证其他事务不会再并发修改这条记录。(select... for update 或lock in share mode,insert/delete/update)

    每个事务都会包含三个隐藏字段:隐含ID、事务号Tid和回滚指针Roll_PT,每次写更新都会在事务ID上增加1


    19.锁可以有哪些分类?锁机制是如何实现得?乐观锁,悲观锁是如何实现的?

    在 MySQL中锁有很多不同的分类。根据不通的划分规则:

    根据锁的粒度可分为表级锁、行级锁和页级锁。

    表级锁:每次操作锁住整张表。锁定粒度大,发生锁冲突的概率最高,并发度最低。应用在MyISAM、InnoDB、BDB 等存储引擎中。

    行级锁:每次操作锁住一行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在InnoDB 存储引擎中。

    页级锁:每次锁定相邻的一组记录,锁定粒度界于表锁和行锁之间,开销和加锁时间界于表锁和行锁之间,并发度一般。应用在BDB 存储引擎中。

    根据锁的操作行为分为读锁和写锁,从操作的类型可分为读锁和写锁。

    读锁(S锁):共享锁,针对同一份数据,多个读操作可以同时进行而不会互相影响。

    写锁(X锁):排他锁,当前写操作没有完成前,它会阻断其他写锁和读锁。

    根据锁的的实现方式分为悲观锁和乐观锁从操作的性能可分为乐观锁和悲观锁。

    乐观锁:一般的实现方式是对记录数据版本进行比对,在数据更新提交的时候才会进行冲突检测,如果发现冲突了,则提示错误信息。

    悲观锁:在对一条数据修改的时候,为了避免同时被其他人修改,在修改数据之前先锁定,再修改的控制方式。共享锁和排他锁是悲观锁的不同实现,但都属于悲观范畴。

    从广义上来讲,前面提到的行锁、表锁、读锁、写锁、共享锁、排他锁等,这些都属于悲观锁范畴。

     

    20.什么情况下会产生死锁?如何避免死锁? 如何解决死锁?  https://blog.csdn.net/qq_34107571/article/details/78001309

    死锁的标准定义:是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

    死锁产生必须要具备以下四个条件:

    互斥条件:某资源在一段时间内只由一个进程占用

    相互等待条件:指在发生死锁时,必然存在一个进程和资源的环形链,比如 A进程正在等待一个B进程占用的资源;B进程又正在等待A进程占用的资源

    不可剥夺条件:指进程已经获取的资源,在未使用完之前,只能由自身释放

    请求和保持:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放

    以下几种情况下会出现死锁:

    情况一:

    如果在事务中执行了一条没有索引条件的查询,引发全表扫描,把行级锁上升为全表记录锁定(等价于表级锁),多个这样的事务执行后,就很容易产生死锁和阻塞

    解决方案:

    SQL语句中不要使用太复杂的关联多表的查询;

    使用explain“执行计划"对SQL语句进行分析,对于有全表扫描和全表锁定的SQL语句,建立相应的索引进行优化。

    情况二:

    两个事务分别想拿到对方持有的锁,互相等待,于是产生死锁。

    解决方案:

    在同一个事务中,尽可能做到一次锁定所需要的所有资源

    按照id对资源排序,然后按顺序进行处理

    四、 如何预防死锁


    如何避免死锁?为此我们在开发的过程中需要遵循如下原则:

    1.编写应用程序避免长事务,让进程持有锁的时间尽可能短

    2.避免并发的执行 修改数据的语句。

    3.要求每一个事务一次就将所有要使用到的数据全部加锁,否则就不允许执行。

    4.预先规定一个加锁顺序,所有的事务都必须按照这个顺序对数据执行封锁。如不同的过程在事务内部对对象的更新执行顺序应尽量保证一致。

    5.使用尽可能低的隔离级别。

    6.数据存储空间离散法。该方法是指采用各种手段,将逻辑上在一个表中的数据分散的若干离散的空间上去,以便改善对表的访问性能。主要通过将大表按行或者列分解为若干小表,或者按照不同的用户群两种方法实现。

      

    怎么查询死锁: https://www.cnblogs.com/jpfss/p/11491526.html

    1、查询进程id,然后kill id  show processlist   kill id 

    2、查询是否锁表 : show OPEN TABLES where In_use > 0;

    3、在5.5中,information_schema 库中增加了三个关于锁的表(innoDB引擎):

    innodb_trx         ## 当前运行的所有事务

    innodb_locks       ## 当前出现的锁

    innodb_lock_waits  ## 锁等待的对应关系

    4、通过show engine innodb status\G命令查看近期死锁日志信息。查看死锁日志


    21.Mysql 主从之间是怎么同步数据的? 主从复制的用途?如何解决主从复制延迟问题?

    主从复制主要应用于:

    故障切换:用于故障切换(高可用)

    数据备份:避免由于数据宕机影响业务(高可用)

    读写分离:提供查询服务(读扩展)

    主从复制通过以下三个步骤完成数据同步:

    1.主库将数据库的变更操作记录到Binlog日志文件中

    2.从库读取主库中的Binlog日志文件信息写入到从库的Relay Log中继日志中

    3.从库读取中继日志信息在从库中进行Replay,更新从库数据信息

    在上述三个过程中,涉及了Master的BinlogDump Thread和   Slave的I/O Thread、SQL Thread,它们的作用如下:

    Master服务器对数据库更改操作记录在Binlog中,BinlogDump Thread接到写入请求后,读取Binlog信息推送给Slave的I/O Thread。

    Slave的I/O Thread将读取到的Binlog信息写入到本地Relay Log中。

    Slave的SQL Thread检测到Relay Log的变更请求,解析relay log中内容在从库上执行


    22.mysql主从复制存在的问题,如何解决主从复制延迟问题?

    mysql主从复制存在的问题:

    主库宕机后,数据可能丢失

    从库只有一个SQL Thread,主库写压力大,复制很可能延时

    解决方法:

    半同步复制---解决数据丢失的问题

    半同步复制: 为了提升数据安全,MySQL让Master在某一个时间点等待Slave节点的 ACK(Acknowledgecharacter)消息,接收到ACK消息后才进行事务提交。

    并行复制----解决从库复制延迟的问题

    并行复制在从库中有两个线程IO Thread和SQL Thread,都是单线程模式工作,因此有了延迟问题,我们可以采用多线程机制来加强,减少从库复制延迟。(IO Thread多线程意义不大,主要指的是SQL Thread多线程)

    在MySQL的5.6、5.7、8.0版本上,都是基于上述SQL Thread多线程思想,不断优化,减少复制延迟。


    23.数据库如何实现分库分表?什么是分库分表?分库分表的策略是什么?

    存在的问题:

    业务越来越大,单表数据超出了数据库支持的容量

    Tps过大,十几万并发量,传统的架构(一主多从),主库容量肯定无法满足这么高的Tps

    拆分的模式:水平拆分 垂直拆分 都属于物理空间的拆分。

    垂直拆分:由于表数量多导致的单个库大。将表拆分到多个库中。

    水平拆分:由于表记录多导致的单个库大。将表记录拆分到多个表中。


      

    面试八股文: https://blog.csdn.net/o9109003234/article/details/121026489

     

    Mysql的集群架构

    Mysql的集群方案有以下三种:  主从架构  双主架构  分库分表

    主从架构(如何实现,如何保证数据一致性)

    主从模式:是指数据可以从一个主数据库复制到一个或多个从节数据库。MySQL 默认采用异步复制方式

    主从架构的作用:

    读写分离,提供查询服务(读扩展)

    数据备份,避免影响业务(高可用)

    实时灾备,用于故障切换(高可用)

    主从复制整体分为以下三个步骤:

    1.主库将数据库的变更操作记录到Binlog日志文件中

    2.从库读取主库中的Binlog日志文件信息写入到从库的 Relay Log中继日志中

    3.从库读取中继日志信息在从库中进行Replay(数据恢复),更新从库数据信息

    在上述三个过程中,涉及了三个线程, Master的BinlogDump Thread和Slave的I/O Thread、SQL Thread,它们的作用如下:

    BinlogDump Thread(binlog日志推送线程) : 当Master服务器对数据库更改操作记录在Binlog中,该线程接到写入请求后,读取Binlog信息推送给Slave的I/O Thread。

    I/O Thread(从库IO线程): 该线程读取到的Binlog信息写入到本地Relay Log中。

    SQL Thread(从库sql线程): Slave的SQL Thread检测到Relay Log的变更请求,解析relay log中内容在从库上执行。

    上述过程都是异步操作,俗称异步复制,存在数据延迟现象。


    主从复制存在的问题(也就是异步操作导致的问题):

    主库宕机,可能导致数据丢失问题:

    数据延迟问题:从库只有一个SQL Thread,主库写压力大,复制很可能延时

    如何主从复制存在的上述问题:

    半同步复制---解决数据丢失的问题

    为了提升数据安全,MySQL可以让Master在某一个时间点等待Slave节点的 ACK消息,接收到ACK消息后才进行事务提交,这也是半同步复制的基础,

    MySQL从5.5版本开始引入了半同步复制机制来降低数据丢失的概率。

    介绍半同步复制之前先快速过一下 MySQL 事务写入碰到主从复制时的完整过程,主库事务写入分为 4个步骤:

    InnoDB Redo File Write (Prepare Write)  两阶段提交中的prepare阶段

    Binlog File Flush & Sync to Binlog File 两阶段提交中的写入binlog阶段

    InnoDB Redo File Commit(Commit Write) 两阶段提交中的写入commit阶段

    Send Binlog to Slave: 主库通过binlogDump thread 推送binlog日志到从库

    当Master不需要关注Slave是否接受到Binlog Event时,即为传统的主从复制。

    当Master需要在第三步等待Slave返回ACK时,即为 after-commit,半同步复制(MySQL 5.5引入)。

    当Master需要在第二步等待 Slave 返回 ACK 时,即为 after-sync,增强半同步(MySQL 5.7引入)

    半同步复制:主库等待从库写入 relay log 并返回 ACK 后才进行Engine Commit。

    并行复制----解决从库复制延迟的问题,通过增加 SQL Thread线程数实现 

    复制延迟的根本原因:

    在从库中有两个线程IO Thread和SQL Thread,都是单线程模式工作,因此有了延迟问题;

    我们可以采用多线程机制来加强,减少从库复制延迟。(IO Thread多线程意义不大,主要指的是SQL Thread多线程:解析redaylog中的内容,并在从库上执行)

    在MySQL的5.6、5.7、8.0版本上,都是基于上述SQL Thread多线程思想,不断优化,减少复制延迟。

    上面说主从复制的根本目的是为了实现 读写分离:

    在读写分离的应用场景下, 可以在从库追加多个索引来优化查询,主库这些索引可以不加,用于提升写效率。

    为了避免主从同步出现的延迟性问题出现,我们也可以使用以下方案:

    写后立刻读

    在写入数据库后,某个时间段内读操作就去主库,之后读操作访问从库。

    二次查询

    先去从库读取数据,找不到时就去主库进行数据读取。该操作容易将读压力返还给主库,

    根据业务特殊处理

    根据业务特点和重要程度进行调整,比如重要的,实时性要求高的业务数据读写可以放在主库。对于次要的业务,实时性要求不高可以进行读写分离,查询时去从库查询。

     

    查看全部
    1 采集 收起 来源:课程介绍

    2023-01-12

  • call

    查看全部
  • JVM

    1.说一下 JVM 的主要组成部分?及其作用?

    类加载器(ClassLoader)

    运行时数据区(Runtime Data Area)

    执行引擎(Execution Engine)

    本地库接口(Native Interface)

    「组件的作用:」 

    1.首先通过类加载器(ClassLoader)会把 Java 代码转换成字节码,再把字节码加载到内存中,ClassLoader只负责class文件的加载,至于它是否可以运行,则由执行引擎决定;

    2.运行时数据区(Runtime Data Area),存储运行时数据

    3.执行引擎(Execution Engine):将字节码翻译成底层系统指令,再交由 CPU 去执行

    4.本地库接口(Native Interface)而这个过程中需要调用其他语言的本地库接口(Native Interface)来实现整个程序的功能。


    2.说一下 JVM 运行时数据区?分为 线程共享数据区 和 线程私有数据区

    1.程序计数器:当前线程所执行的字节码的行号指示器,字节码解析器的工作是通过改变这个计数器的值,来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能,都需要依赖这个计数器来完成;

    2.虚拟机栈:用于存储局部变量表、操作数栈、动态链接、方法出口等信息;

    3.本地方法栈:与虚拟机栈的作用是一样的,只不过虚拟机栈是服务Java 方法的,而本地方法栈是为虚拟机调用 Native 方法服务的;

    4.Java 堆(Java Heap):Java 虚拟机中内存最大的一块,是被所有线程共享的,几乎所有的对象实例都在这里分配内存;

    5.方法区(Methed Area):用于存储已被虚拟机加载的 类信息、常量、静态变量、即时编译后的代码等数


    3.说一下堆栈的区别?

    功能方面:堆是用来存放对象的,栈是用来执行程序的,比如记录方法的进栈和出栈。 栈帧

    共享性:堆是线程共享的,栈是线程私有的。

    空间大小:堆大小远远大于栈。

    4.类加载器的作用是什么?

    1类的加载指的是将类的.class文件中的二进制数据读入到内存中,

    2将其放在运行时数据区的方法区内,

    3然后在创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。

    5.JVM有哪几种类加载器?

    类加载器:对于任意一个类,都需要由加载它的类加载器和这个类本身一同确立在 JVM 中的唯一性,每一个类加载器,都有一个独立的类名称空间。

    类加载器就是根据指定全限定名称将 class 文件加载到 JVM 内存,然后再转化为 class 对象。

    类加载器分类:

    启动类加载器(Bootstrap ClassLoader),是虚拟机自身的一部分,用来加载Java_HOME/lib/目录中的,或者被 -Xbootclasspath 参数所指定的路径中并且被虚拟机识别的类库;

    扩展类加载器(Extension ClassLoader):负责加载libext目录或Java. ext. dirs系统变量指定的路径中的所有类库;

    应用程序类加载器(Application ClassLoader):负责加载用户类路径(classpath)上的指定类库,我们可以直接使用这个类加载器。一般情况,如果我们没有自定义类加载器默认就是用这个加载器。


    6.什么是双亲委派模型?为什么需要双亲委派模型?

    双亲委派模型:

    如果一个类加载器收到了类加载的请求,它首先不会自己去加载这个类,向父类加载器去完成,每一层的类加载器都是如此,这样所有的加载请求都会被传送到顶层的启动类加载器中,

    只有当父加载无法完成加载请求(它的搜索范围中没找到所需的类)时,子加载器才会尝试去加载类。

    假设没有双亲委派模型,试想一个场景:

    黑客自定义一个java.lang.String 类,该String 类具有系统的String 类一样的功能,只是在某个函数稍作修改。比如equals 函数,这个函数经常使用,如果在这这个函数中,黑客加入一些“病毒代码”。

    并且通过自定义类加载器加入到JVM 中。此时,如果没有双亲委派模型,那么JVM 就可能误以为黑客自定义的java.lang.String 类是系统的String 类,导致“病毒代码”被执行。

    而有了双亲委派模型,黑客自定义的java.lang.String 类永远都不会被加载进内存。

    因为首先是最顶端的类加载器加载系统的java.lang.String类,最终自定义的类加载器无法加载java.lang.String 类。

    或许你会想,我在自定义的类加载器里面强制加载自定义的java.lang.String 类,不去通过调用父加载器不就好了吗?确实,这样是可行。

    但是,在JVM 中,判断一个对象是否是某个类型时,不仅会比较类的全限定名,也会比较类加载器是否一致。


    7.说一下类装载的执行过程?

    三个阶段:加载阶段  连接阶段  初始化阶段

    类从被加载到虚拟机内存中开始,到卸载出内存,它的整个生命周期包括以下7个步骤:

    加载:根据查找路径找到相应的 class 文件然后导入;加载阶段可以细致分为:

    jvm启动时加载:加载的是JAVA_HOME/lib/下的rt.jar下的.class文件,这个jar包里面的内容是程序运行时非常常常用到的,像java.lang.*、java.util.、java.io. 等等,因此随着虚拟机一起加载  

    运行时加载:虚拟机在用到一个.class文件的时候,会先去内存中查看一下这个.class文件有没有被加载,如果没有就会按照类的全限定名来加载这个类。

    验证:检查加载的 class 文件的正确性;确保.class文件的字节流中包含的信息符合当前虚拟机的要求

    准备:给类中的静态变量分配内存空间;为类变量分配内存并设置其初始值的阶段,这些变量所使用的内存都将在方法区中分配

    解析:虚拟机将常量池中的符号引用替换成直接引用的过程。符号引用就理解为一个标示,而在直接引用直接指向内存中的地址;

    初始化:对静态变量和静态代码块执行初始化工作。编译器收集的顺序是由语句在源文件中出现的顺序决定的

    使用:

    卸载:卸载属于GC的工作,

    类卸载的时机

    1. 该类所有的实例已经被回收

    2. 加载该类的ClassLoder已经被回收

    3. 该类对应的java.lang.Class对象没有任何对方被引用

    8.怎么判断对象是否可以被回收?

    一般有两种方法来判断:

    1引用计数器:为每个对象创建一个引用计数,有对象引用时计数器 +1,引用被释放时计数 -1,当计数器为 0 时就可以被回收。它有一个缺点不能解决循环引用的问题;

    优点:

    实现简单,执行效率高,很好的和程序交织。

    缺点:

    无法检测出循环引用。

    譬如有A和B两个对象,他们都互相引用,除此之外都没有任何对外的引用,那么理论上A和B都可以被作为垃圾回收掉,

    但实际如果采用引用计数算法,则A、B的引用计数都是1,并不满足被回收的条件,如果A和B之间的引用一直存在,那么就永远无法被回收了

    2可达性分析:从 GC Roots 开始向下搜索,搜索所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连时,则证明此对象是可以被回收的。

    有哪些对象可以作为GC Roots根   栈帧中的局部变量表中引用的对象  方法区中static静态引用的对象    方法区中final静态引用的对象 本地方法栈中引用的对象

    9.在可达性分析算法中判定对象为不可达的对象,对象“非死不可”吗?

    即使在可达性分析算法中判定为不可达的对象, 也不是“非死不可”的, 这时候它们暂时还处于“缓 刑”阶段, 要真正宣告一个对象死亡, 至少要经历两次标记过程:

    第一次标记:

    发现没有与GC Roots相连接的引用链, 那它将会被第一次标记,随后进行筛选是否需要执行finalize()方法,判断为没有必要执行finalize()方法后直接被回收

    第二次标记:

    执行finalize()方法,将对象放入到F-Queue队列中,在清理对象前执行第二次标记

     

      

    10.Java 中都有哪些引用类型?引用类型的使用场景 

    强引用:发生 gc 的时候不会被回收,当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误强引用其实也就是我们平时A a = new A()这个意思。

    软引用:非必需但仍有用的对象,比如缓存信息 有用但不是必须的对象,在发生内存溢出之前会被回收。

    弱引用:无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收。当强引用不存在时,帮助gc回收。比如ThreadLocal的实现

    虚引用:无法通过虚引用获得对象,用 PhantomReference 实现虚引用,虚引用的用途是在 gc 时返回一个通知。

    11.说一下 JVM 有哪些垃圾回收算法?

    1标记-清除算法:标记无用对象,然后进行清除回收。缺点:效率不高,无法清除垃圾碎片。

    第一个是执行效率不稳定, 如果Java堆中包含大量对 象, 而且其中大部分是需要被回收的, 这时必须进行大量标记和清除的动作, 导致标记和清除两个过 程的执行效率都随对象数量增长而降低;

    第二个是内存空间的碎片化问题, 标记、 清除之后会产生大 量不连续的内存碎片, 空间碎片太多可能会导致当以后在程序运行过程中需要分配较大对象时无法找 到足够的连续内存而不得不提前触发另一次垃圾收集动作。

    2复制算法:按照容量划分二个大小相等的内存区域,当一块用完的时候将活着的对象复制到另一块上,然后再把已使用的内存空间一次清理掉。缺点:内存使用率不高,只有原来的一半。

    3标记-整理算法:标记无用对象,让所有存活的对象都向一端移动,然后直接清除掉端边界以外的内存。

    4分代算法:根据对象存活周期的不同将内存划分为几块,一般是新生代和老年代,新生代基本采用复制算法,老年代采用标记整理算法。 

      HotSpot虚拟机默认Eden和Survivor的大小比例是8∶1, 也即每次新生代中可用内存空间为整个新 生代容量的90%(Eden的80%加上一个Survivor的10%) , 只有一个Survivor空间, 即10%的新生代是会 被“浪费”的。

    在Java堆划分出不同的区域之后, 垃圾收集器才可以每次只回收其中某一个或者某些部分的区域 ——因而才有了“young gc ”“old gc”“Full GC”这样的回收类型的划分; 

    也才能够针对不同的区域安 排与里面存储对象存亡特征相匹配的垃圾收集算法——因而发展出了“标记-复制算法”“标记-清除算 法”“标记-整理算法”等针对性的垃圾收集算法。

    针对不同分代的类似名词, 为避免产生混淆, 在这里统一定义 :

    部分收集(Partial GC) : 指目标不是完整收集整个Java堆的垃圾收集, 其中又分为:

    - 新生代收集(Minor GC/Young GC): 指目标只是新生代的垃圾收集。

    - 老年代收集(Major GC/Old GC): 指目标只是老年代的垃圾收集,目前只有CMS收集器会有单 独收集老年代的行为。

    - 混合收集(Mixed GC): 指目标是收集整个新生代以及部分老年代的垃圾收集。 目前只有G1收集器会有这种行为。

    整堆收集(Full GC) : 收集整个Java堆和方法区的垃圾收集

     

    12.垃圾回收算法分类两类,

    第一类算法判断对象生死算法,

    如引用计数法、可达性分析算法 ;

    第二类收集死亡对象方法有四种,

    如标记-清除算法、标记-复制算法、标记-整理算法。一般的实现采用分代回收算法,根据不同代的特点应用不同的算法。

    垃圾回收算法是内存回收的方法论。垃圾收集器是算法的落地实现。和回收算法一样,目前还没有出现完美的收集器,而是要根据具体的应用场景选择最合适的收集器,进行分代收集。

    13.说一下 JVM 有哪些垃圾回收器?

    1新生代串行收集器   Serial:最早的单线程串行垃圾回收器。

    2老年代串行收集器   Serial Old:Serial 垃圾回收器的老年版本,同样也是单线程的,可以作为 CMS 垃圾回收器的备选预案。

    3新生代ParNew收集器:是 Serial 的多线程版本。

    4新生代并行收集器:Parallel 使用的是复制的内存回收算法

    5老年代并行收集器:Parallel Old 使用的是标记-整理的内存回收算法

    6CMS垃圾收集器:一种以获得最短停顿时间为目标的收集器,非常适用 B/S 系统。

    7G1垃圾收集器:一种兼顾吞吐量和停顿时间的 GC 实现,是 JDK 9 以后的默认 GC 选项

    Parallel 和 ParNew 收集器类似是多线程的,但 Parallel 是吞吐量优先的收集器,可以牺牲等待时间换取系统的吞吐量。

     

    新生代垃圾收集器:Serial 、 ParNew 、Parallel Scavenge

    老年代垃圾收集器:Serial Old 、 Parallel Old 、CMS

    整理收集器:G1

     

    14.详细介绍一下 CMS 垃圾回收器?

    CMS 是英文 Concurrent Mark-Sweep 的简称,是以牺牲吞吐量为代价来获得最短回收停顿时间的垃圾回收器。对于要求服务器响应速度的应用上,这种垃圾回收器非常适合。

    在启动 JVM 的参数加上“-XX:+UseConcMarkSweepGC”来指定使用 CMS 垃圾回收器。

    -XX:+UseConcMarkSweepGC 指定使用CMS垃圾回收器,使用 ParNew + CMS + Serial Old 的收集器组合 

    CMS 使用的是标记-清除的算法实现的,所以在 gc 的时候回产生大量的内存碎片,当剩余内存不能满足程序运行要求时,系统将会出现 Concurrent Mode Failure,临时 CMS 会采用 Serial Old 回收器进行垃圾清除,此时的性能将会被降低。

    CMS垃圾收集的过程分为四个阶段: 初始标记  并发标记 重新标记  并发清除 其中 初始标记和重新标记都需要stopTheWord

    初始标记的作用:标记所有能够被GC Roots关联到的对象,一旦标记完成就结束

    并发标记的作用:从初始标记过程中被GC Roots关联的对象开始遍历整个对象图的过程,过程比较耗时,并发执行

    重新标记的作用:维护并发标记期间新产生的标记记录

    并发清除的作用:清除垃圾

    由于最消耗事件的并发标记与并发清除阶段都不需要暂停工作,因为整个回收阶段是低停顿(低延迟)的。 简述分代垃圾回收器是怎么工作的?

    缺点:

    1.会产生空间碎片

    2.CMS收集器无法处理浮动垃圾,可能出现"Concurrent Mode Failure"失败而导致另一次Full GC的产生。


    15.简述分代垃圾回收器是怎么工作的?G1垃圾收集器

    分代回收器有两个分区:老生代和新生代,新生代:老生代=1:2

    新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,可以通过 SurvivorRatio属性 设置新生代中 Eden 和任何一个 Survivor 区域的容量比值,默认为 8

    它的执行流程如下:

    把 Eden + From Survivor 存活的对象放入 To Survivor 区;

    清空 Eden 和 From Survivor 分区;

    From Survivor 和 To Survivor 分区交换,From Survivor 变 To Survivor,To Survivor 变 From Survivor。

    每次在 From Survivor 到 To Survivor 移动时都存活的对象,年龄就 +1,当年龄到达 15(默认配置是 15)时,升级为老生代。大对象也会直接进入老生代。

    老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程

    16.内存分配原则:

    虚拟机对于内存的分配还是会遵循以下几种「普世」规则:

    1.对象优先在 Eden 区分配

    多数情况,对象都在新生代 Eden 区分配。当 Eden 区分配没有足够的空间进行分配时,虚拟机将会发起一次 Minor GC。如果本次 GC 后还是没有足够的空间,则将启用分配担保机制在老年代中分配内存。

    这里我们提到 Minor GC,如果你仔细观察过 GC 日常,通常我们还能从日志中发现 Major GC/Full GC。

    Minor GC 是指发生在新生代的 GC,因为 Java 对象大多都是朝生夕死,所有 Minor GC 非常频繁,一般回收速度也非常快;

    Major GC/Full GC 是指发生在老年代的 GC,出现了 Major GC 通常会伴随至少一次 Minor GC。Major GC 的速度通常会比 Minor GC 慢 10 倍以上。

    2.大对象直接进入老年代

    所谓大对象是指需要大量连续内存空间的对象,频繁出现大对象是致命的,会导致在内存还有不少空间的情况下提前触发 GC 以获取足够的连续空间来安置新对象。

    前面我们介绍过新生代使用的是标记-清除算法来处理垃圾回收的,如果大对象直接在新生代分配就会导致 Eden 区和两个 Survivor 区之间发生大量的内存复制。因此对于大对象都会直接在老年代进行分配。


    3.长期存活对象将进入老年代

    虚拟机采用分代收集的思想来管理内存,那么内存回收时就必须判断哪些对象应该放在新生代,哪些对象应该放在老年代。因此虚拟机给每个对象定义了一个对象年龄的计数器,

    如果对象在 Eden 区出生,并且能够被 Survivor 容纳,将被移动到 Survivor 空间中,这时设置对象年龄为 1。对象在 Survivor 区中每「熬过」一次 Minor GC 年龄就加 1,当年龄达到一定程度(默认 15) 就会被晋升到老年代。

    17.JVM 提供的常用工具

    命令行工具:

    jps:   用来显示本地的 Java 进程,,查看java进程及其相关的信息,如果你想找到一个java进程的pid,那可以用jps命令替代linux中的ps命令了

    jinfo: 用来查看JVM参数和动态修改部分JVM参数的命令:Java System 属性 和 JVM 命令行参数,Java class path 等信息。 命令格式:jinfo 进程 pid

    jstat: 监视虚拟机各种运行状态信息的命令行工具。 命令格式:jstat -gc 123 250 20

    jstack:可以观察到 JVM 中当前所有线程的运行情况和线程当前状态。 命令格式:jstack 进程 pid

    jmap:  可以生成 java 程序的 dump 文件, 也可以查看堆内对象示例的统计信息、查看 ClassLoader 的信息以及finalizer 队列,观察运行中的 JVM 物理内存的占用情况(如:产生哪些对象,及其数量)。 命令格式:jmap [option] pid

    jhat: jhat是用来分析jmap生成dump文件的命令,jhat内置了应用服务器,可以通过网页查看dump文件分析结果,jhat一般是用在离线分析上。

    可视化UI工具:

    jconsole:

    在JDK中自带的java监控和管理控制台,用于对JVM中内存,线程和加载类的数量等的监控, jconsole使用jvm的扩展机制获取并展示虚拟机中运行的应用程序的性能和资源消耗等信息。直接在jdk/bin目录下点击jconsole.exe即可启动

    JVisualVM:

    显示虚拟机进程和进程的配置、环境信息(jps、jinfo)

    监视应用程序的CPU、GC、堆、方法区及线程的信息(jstat、jstack)

    dump及分析堆转储快照(jmap、jhat)

    方法级的程序运行性能分析, 找出被调用最多、运行时间最长的方法

    离线程序快照: 收集程序的运行时配置、线程dump、内存dump等信息建立一个快照, 可以将快照发送开发者处

    进行bug反馈等等

     

     

    18.如何排查CPU使用率过高的问题?在某一天系统更新上线一个版本后,运维人员通过监控发现服务器出现CPU使用率超过200%的问题,导致监控系统频繁告警。

    可参考:https://blog.csdn.net/changqing5818/article/details/121096250

    查看服务器信息

    2.1 找出耗CPU的进程: top命令查看各进程信息

    使用top命令查看特定用户(user1)的内存、cpu及各进程的信息:

    2.2 进一步确认进程信息为 Java线程

    jps -m  (-m : 显示进程id, 显示JVM启动时传递给main()的参数)   使用jps指令可以快速找java进程的pid

    或者 ps|grep xxxx (ps 命令的作用是显示进程信息的;| 符号,是个管道符号,表示ps 和 grep 命令同时执行)

    2.3 然后查出消耗CPU的线程

    通过 top -HP pid(指定进程id) 找到耗时最高的线程和占用CPU的时间 

    得到耗CPU较高的线程ID,对找到%CPU使用率较高的线程逐一分析。

    2.4 找到了耗时较高的线程ID,下面通过JVM的堆栈信息找到线程信息,使用下面的命令可以获得JVM的堆栈信息

    jstack pid 查看堆栈信息   (jstack命令通常用来 排查CPU占用过高 和线程死锁问题)


    CPU使用率过高的原因,大概有以下几种情况:

    1、Java 内存不够或溢出导致GC overhead问题,GC overhead 导致的CPU 100%问题;

    2、死循环问题,如常见的HashMap被多个线程并发使用导致的死循环 或者 死循环代码;

    3、某些操作一直占用CPU

    大多数都是因为线程无法终止或出现死循环等原因,但是仍然需要根据实际情况,具体问题具体分析

     

     

    查看全部
    0 采集 收起 来源:课程介绍

    笔记审核中笔记正在审核中,仅自己可见 2023-01-09

  • 1.JDBC问题总结:

    原始jdbc开发存在的问题如下:

    1、 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。

    2、 Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变java代码。

    3、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析⽐较⽅便

    2.为什么说Mybatis是半自动ORM映射工具,?它与全自动的区别在哪里?

    ORM是指对象关系映射(对象关系模型)

    Mybatis是半自动ORM映射工具,它在执行sql时需要手动编写sql语句,所以,称之为半自动 ORM 映射工具。

    Hibernate 属于全自动 ORM 映射工具,使用 Hibernate 查询关联对象或者关联集合对象时,可以根据 对象关系模型 直接获取,所以它是全自动的。

     

    3.mybatis的架构模型       

    mybatis的架构分为三层,API接口层  数据处理层 框架支撑层

    其中:

    API接口层: 提供给外部使⽤的接⼝API,开发⼈员通过这些本地API来操纵数据库,接⼝层⼀接收到调⽤请求就会调⽤数据处理层来完成具体的数据处理。可以简单理解为是我们编写的mapper文件 或者使用sqlSession对象进行的CRUD。

    数据处理层:完成数据的处理 包括 sql解析 参数映射 sql执行 返回结果映射,主要目的是根据调用请求完成一次数据库操作

    框架支撑层:负责最基础的功能支撑,比如: 缓存 事务 数据库连接池 xml文件解析,配置文件加载等功能


    4.mybatis的核心组件以及相互关系  

    1.SqlSession:

    作为MyBatis⼯作的主要顶层API,完成对数据库CRUD功能,底层通过 Executor 对象完成数据库操作,如内部方法 selectOne selectLsit update insert等方法

    2.Executor:  

    sql执⾏器,是MyBatis调度的核⼼,负责SQL语句的⽣成执行和查询缓存的维护  比如  query createCacheKey commit  rollback等方法

    3.StatementHandler:

    封装了JDBC Statement操作,负责对JDBC statement的操作,如设置参数、将获取的结果集转换成List集合。

    (JDBC的对象statement 或者  preparedStatement 对象封装sql语句,并通过execute方法将sql语句发送给数据库并获取结果,获取的结果通过ResultSet接收)

    4.ParameterHandler:参数处理器

    负责将用户传递过来的参数转换为转换成JDBC Statement所需要的参数,

    5.ResultSetHandler:结果处理器

    负责将JDBC返回的ResultSet结果集对象转换成对应的java对象或List类型的集合;

    6.TypeHandler:类型处理器

    负责java数据类型和jdbc数据类型之间的映射和转换

    7.MappedStatement:

    MappedStatement 维护了⼀条<select | update | delete | insert>节点的封装

    8.SqlSource:sql源

    负责根据⽤户传递的parameterObject,动态地⽣成SQL语句,将信息封装到BoundSql对象中,并返回

    9.BoundSql:

    表示动态⽣成的SQL语句以及相应的参数信息

    5.mybatis的工作处理流程:  

    (1) 加载配置并初始化

    触发条件:加载配置⽂件(主配置文件和mapper文件或者是注解 )将主配置⽂件内容解析封装到Configuration,将sql的配置信息加载成为⼀个mappedstatement对象,存储在内存之中

    (2) 接收调⽤请求

    触发条件:调⽤Mybatis提供的API ,比如通过Mapper接口调用 或者sqlSession顶层API 接口进行调用

    处理过程:将请求传递给下层的请求处理层进⾏处理。(也就是交给数据处理层进行 处理)

    (3) 处理操作请求 会涉及Executor、StatementHandler、ParameterHandler、ResultSetHandler BoundSql等对象的执行

    触发条件:API接⼝层传递请求过来 

    处理过程:

    (A) 根据SQL的ID查找对应的MappedStatement对象。 (SQL 的 ID 就是  mapper文件的全限定类名+ID)

    (B) 根据传⼊参数对象解析MappedStatement对象,得到最终要执⾏的SQL和执⾏传⼊参数。(要执行的SQL语句存放在SqlSource对象中,SqLSource对象根据请求参数动态生sql语句,并封装到BoundSql中)

    (C) 获取数据库连接,根据得到的最终SQL语句和执⾏传⼊参数到数据库执⾏,并得到执⾏结果。

    (D) 根据MappedStatement对象中的结果映射配置对得到的执⾏结果进⾏转换处理,并得到最终的处理 结果。 (结果映射是由 ResultsetHandler对象负责转换)

    (E) 释放连接资源。

    (4) 返回处理结果,将最终的处理结果返回。

    通过源码层面分析上述流程:

    第一步:加载配置文件并进行初始化

    MyBatis在初始化的时候,会将MyBatis的配置信息全部加载到内存中,使⽤org.apache.ibatis.session.Configuration对象 实例来维护

    解析文件过程:XMLConfigBuilder.parse()-->XMLConfigBuilder#parseConfiguration--->封装得到Configuration对象,

    其中 mapper文件被封装成MappedStatement对象存放在Configuration对象的mappedStatements属性中,mappedStatements 是⼀个HashMap,存储时key=全限定类名+⽅法名,value =对应的MappedStatement对象。

    并且通过MapperRegistry#getMapper方法,通过动态代理机制为Mappe生成代理对象,

    第二步:创建sqlSession对象,通过DefaultSqlSessionFactory#openSession()获取到sqlSession对象,并使用该对象调用请求,比如(sqlSession.selectList())

    DefaultSqlSessionFactory#openSessionFromDataSource 方法创建出指定了Excutor类型的session对象,然后调用方法, 比如(sqlSession.selectList())

    首先根据传⼊statement参数(mapper文件的全限定名+⽅法名)从映射的Map中取出MappedStatement对象,

    然后通过 Executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER) 进行sql查询

    第三步:处理操作请求(分析sqlSession.selectList()是如何执行的呢?)

    CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler)方法执行

    根据传入的参数获取动态sql:BoundSql对象

    创建一个缓存的key: cacheKey

    6.mapper接口开发需要遵循哪些规范? mybatis是在什么时间节点进行动态代理?

    Mapper接口开发需要遵循以下规范:

    1、 Mapper.xml文件中的namespace与mapper接口的类路径相同。

    2、 Mapper接口方法名和Mapper.xml中定义的每个statement的id相同

    3、 Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同

    4、 Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同

    MyBatis初始化时对接⼝的处理:MapperRegistry是Configuration中的⼀个属性,它内部维护⼀个HashMap⽤于存放mapper接⼝的⼯⼚类,每个接⼝对应⼀个⼯⼚类。mappers中可以配置接⼝的包路径,或者某个具体的接⼝类。

    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();



    7.说一下 MyBatis 的一级缓存和二级缓存?

    一级缓存:

    基于 PerpetualCache 的 HashMap 本地缓存,它的生命周期是和session 一致的,默认开启,

    一级缓存的创建和删除:

    创建:通过Excutor类执行query方法时,使用createCacheKey方法完成缓存的创建,查询方法会先查询是否存在缓存,不存在再查询数据库,并将查询结果添加到perpetualCache中,

    删除:1.通过Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,2.执行update commit rollback 等方法也会删除缓存;

    二级缓存:

    也是基于 PerpetualCache 的 HashMap 本地缓存,作用域为Mapper的nameSpace级别的,默认是关闭的,如果需要开启二级缓存,设置enableCache= true;

    mybatis⾃带的⼆级缓存,但是这个缓存是单服务器⼯作,⽆法实现分布式缓存,因此mybatis也预留接口,允许用户通过实现Cache接口,自定义二级缓存

    开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。

     

    8.mybatis的拦截机制?实现自定义插件?

    MyBatis⽀持⽤插件对四⼤核⼼对象进拦截,对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四⼤对象都是代理对象,拦截器添加在Configuration的intecptors属性上;

    以此要实现自定义参加需要实现intecptor接口,并添加到拦截器链中

    MyBatis 自定义插件针对 MyBatis 四大对象(Executor、StatementHandler、ParameterHandler、ResultSetHandler)进行拦截:

    1.Executor:拦截内部执行器,它负责调用 StatementHandler 操作数据库,并把结果集通过 ResultSetHandler 进行自动映射,另外它还处理了缓存的操作;支持拦截的方法:update、query、commit、rollback

    2.statementHandler:拦截 SQL 语法构建的处理,它是 MyBatis 直接和数据库执行 SQL 脚本的对象,另外它也实现了 MyBatis 的一级缓存; 支持拦截的方法:prepare、parameterize、batch、updates query

    3.ParameterHandler:拦截参数的处理; 支持拦截的方法  getParameterObject、setParameters

    4.ResultSetHandler:拦截结果集的处理。支持拦截的方法 handleResultSets、handleOutputParameters

    如集团的加密平台,底层通过mybatis的插件机制实现的字段加解密


    9.mybatis中有哪些设计模式

    责任链设计模式:

    插件涉及到的四大对象:Excutor StatementHander ParameterHandel ResultHandel,通过拦截器链的形式,组成责任链设计模式

    建造者模式 

    例如SqlSessionFactoryBuilder、Environment;

    ⼯⼚⽅法模式 

    例如SqlSessionFactory、TransactionFactory、LogFactory

    代理模式 Mybatis实现的核⼼,

    ⽐如MapperProxy、ConnectionLogger,⽤的jdk的动态代理还有executor.loader包使⽤了 cglib或者javassist达到延迟加载的效果

    模板⽅法模式

    例如 BaseExecutor 和 SimpleExecutor,还有 BaseTypeHandler 和所有的⼦类例如IntegerTypeHandler;

    适配器模式

    例如Log的Mybatis接⼝和它对jdbc、log4j等各种⽇志框架的适配实现;


    10.MyBatis 有哪些执行器(Executor)?它们之间有哪些区别?如何指定执行器呢?

    MyBatis有三种基本的Executor执行器,实现了Excutor接口:

    1.SimpleExecutor: 每执行一次 update 或 select 就开启一个 Statement对象,用完立刻关闭 Statement 对象;默认实现的执行器

    2.BatchExecutor : 执行 update(没有 select,jdbc 批处理不支持 select),将所有 SQL 都添加到批处理中(addBatch()),等待统一执行(executeBatch()),

           它缓存了多个 Statement 对象,每个 Statement 对象都是 addBatch()完毕后,等待逐一执行 executeBatch()批处理,与 jdbc 批处理相同;

    3.ReuseExecutor : 执行 update 或 select,以 SQL 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后不关闭 Statement 对象,而是放置于 Map 内供下一次使用。简言之,就是重复使用 Statement 对象;

    Excutor的这些特点,都严格限制在了sqlSession的生命周期范围内

    在mybatis的配置文件中,可以通过settings指定默认的ExecutorType执行器类型,也可以在通过DefaultSqlSessionFactory.opSession(ExecutorType executorType)方法中指定执行器类型


    11.MyBatis 中 #{}和 ${}的区别是什么?

    #{}是预编译处理,${}是字符替换。在使用 #{}时,MyBatis 会将 SQL 中的 #{}替换成“?”,配合 PreparedStatement 的 set 方法赋值,这样可以有效的防止 SQL 注入,保证程序的运行安全。

     

    12. 如何获取生成的主键

    一般来说,我们创建表时,都会设置一个主键字段并设置为主键自增模式,如果需要获取生成的主键,可以将 SQL语句 (只对insert 和 update有效)中的userGenratekey属性设置为 ture,并指定对应的keyProperty 和 keyColumn

    1.useGeneratedKeys:就是让选择是否返回自动生成的主键,默认的是false,如果想要返回主键,那么就自定义为true

    2.KeyProperty     :就是让选择把获取的主键传递给传入的对象的那个属性。

    3.keyColumn       :让选择是从哪一列来获取一个自动生成的主键。比如我们是id,就把id传入。


    13.Mybatis动态sql是做什么的?都有哪些动态sql?能简述一下动态sql的执行原理不?

    Mybatis的动态sql的作用:根据传入条件,动态生成sql语句

    Mybatis动态sql(有哪些)标签:

    <select> <update> <insert> <delete>

    <if>:  

    if是为了判断传入的值是否符合某种规则,比如是否不为空;

    <where>:

    where标签可以用来做动态拼接查询条件,当和if标签配合的时候,不用显示的声明类似where 1=1这种无用的条件;

    <choose><when><otherwise>:

    这是一组组合标签,他们的作用类似于 Java 中的 switch、case、default。只有一个条件生效,也就是只执行满足的条件 when,没有满足的条件就执行 otherwise,表示默认条件;

    <foreach>:

    foreach标签可以把传入的集合对象进行遍历,然后把每一项的内容作为参数传到sql语句中,里面涉及到 item(具体的每一个对象), index(序号), open(开始符), close(结束符), separator(分隔符);

    <include>:

    include可以把大量重复的代码整理起来,当使用的时候直接include即可,减少重复代码的编写;

    <set>:

    适用于更新中,当匹配某个条件后,才会对该字段进行更新操作

    动态sql的执行原理:

    第一部分:在启动加载解析xml配置文件的时候进行解析,根据关键标签封装成对应的handler处理对象,封装成sqlSource对象存在mappedStatement。

    调用流程:

    I、SqlSessionFactoryBuilder对builder对象的时候,调用XMLConfigBuilder解析sqlMapConfig.xml配置文件,在解析过程中使用到了私有的mapperElement(XNode parent)方法

    II、上面方法中通过构建XMLMapperBuilder,获取到所有的配置mapper配置,在调用private void configurationElement(XNode context)方法进行解析mapper.xml,

    通过void buildStatementFromContext(List<XNode> list, String requiredDatabaseId)方法解析mapper.xml内的每一个标签

    III、循环中构建XMLStatementBuilder对象,调用parseStatementNode()方法来封装mappedStatment对象,

    IIII、在过程中需要构建sqlSource对象,通过XMLLanguageDriver对象进行处理,在XMLLanguageDriver中构建解析动态标签对象XMLScriptBuilder


    第二部分:在执行过程中获取sqlSource中获取bondSql对象时,执行相应的标签handler调用查询执行到BaseExecutor的query方法时候会去getBoundSql并且将参数传进去,

    在sqlSource接口  DynamicSqlSource  实现类中,调用getBoundSql方法执行过程共创建DynamicContext对象进行判定解析封装成SqlSource对象返回。


    14.Mybatis映射文件中,如果A标签通过include引用了B标签的内容,请问,B标签能否定义在A标签的后面,还是说必须定义在A标签的前面?TODO

    虽然说mybatis解析xml文件时按顺序解析,但是b标签的位置可以在任何地方。原理:mybatis解析a标签时,发现引用了b标签,未解析到b标签,此时会把a标签标记为未解析状态,

    继续解析下面内容,把剩下解析完之后,再解析标记为未解析的标签,此时已解析到b标签,a标签也就顺利解析完成

    15.当实体类中的属性名和表中的字段名不一样 ,怎么办 TODO

    在Mapper映射文件中使用resultMap来自定义映射规则 ;通过<resultMap> 表情设置 关系映射

     


    16.模糊查询like语句该怎么写

    LIKE concat('%',#{keyword},'%'),MySQL的concat函数可以将多个字符串连接成一个字符串;

     

    17.在mapper中如何传递多个参数

    1.将多个参数定义成一个对象或Map对象,通过对象.属性名来传递

    2.使用注解@Param注解定义多个参数

    18.Mybatis如何执行批量操作

    使用foreach标签,通过遍历入参,拼接出对应的sql。foreach注解中关键属性有 collection item index open close separator

    19.Mybatis的Xml映射文件中,不同的Xml映射文件,id是否可以重复?

    不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;

    原因就是:

    mybait会将mapper.xml文件加载成configuration对象,sql语句标签会被加载成mapperStatement对象,MappedStatement与Mapper配置⽂件中的⼀个select/update/insert/delete节点相对应。

    mapperStatement对象会被Configuration对象会被存储到mappedStatements属性中,mappedStatements 是⼀个HashMap,其中key是namespace+id,如果没有namespace,就剩下id,那么,id重复会导致数据互相覆盖。


     

    20.简述Mybatis的Xml映射文件和Mybatis内部数据结构之间的映射关系?

    Mybatis将所有Mpapper.Xml配置信息都封装到重量级对象Configuration内部。

    在Xml映射文件中,<parameterMap>标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。

    <resultMap>标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。

    每一个<select>、<insert>、<update>、<delete>标签均会被解析为MappedStatement对象,

    标签内的sql会被解析为BoundSql对象


    21.Mybatis是如何进行分页的?分页插件的原理是什么?

    MyBatis⽀持⽤插件对四⼤核⼼对象进⾏拦截,对mybatis来说插件就是拦截器,⽤来增强核⼼对象的功能,增强功能本质上是借助于底层的动态代理实现的

    mybatis 的分页插件是pageHelper,插件原理通过mybatis预留的四大拦截器接口,拦截sql语句,并根据pageHelper设置参数生成对应的limit语句,拼接到sql语句中

    查看全部
    0 采集 收起 来源:课程介绍

    笔记审核中笔记正在审核中,仅自己可见 2023-01-09

  • j

    查看全部
  • 1

    查看全部
  • ccc

    查看全部
    0 采集 收起 来源:什么是多线程

    2022-12-25

  • 并发

    查看全部
    0 采集 收起 来源:课程介绍

    2022-12-25

举报

0/150
提交
取消
课程须知
1.需要掌握基础的Java语法,对Java多线程感兴趣的学员 2.有志于成为资深Java开发工程师的学员
老师告诉你能学到什么?
1.准确理解和掌握线程,进程的概念,使用代码创建线程 2.掌握线程安全的概念,保证线程安全的方法,能开发出线程安全的代码 3.实战中掌握并发编程

微信扫码,参与3人拼团

意见反馈 帮助中心 APP下载
官方微信
友情提示:

您好,此课程属于迁移课程,您已购买该课程,无需重复购买,感谢您对慕课网的支持!