知识点汇总2

312 阅读17分钟

1、什么是MQ?MQ有什么作用?不同的MQ各自有什么优缺点?

什么是MQ

MQ(Message Queue)是消息队列的意思,发送消息、遵循先进先出的原则,可以实现解耦。

MQ的作用

  • 流量削峰:某一时间过多的请求可能导致没办法处理,使用消息队列可以把某一时间过大的流量分散在一个时间段内进行,起到削峰的作用
  • 应用解耦:有时候应用包含几个系统,比如买东西有下单系统、支付系统等等,如果不使用MQ,一个系统的崩溃会造成全部应用无法完成,使用MQ,各个系统对接MQ,某一个系统崩溃不会影响其他系统的正常允许。
  • 异步处理:A调用B可能B需要很长时间响应,MQ可以帮助我们监听并接收B发来的通知,然后发送消息给A,实现异步处理

不同的MQ

  • ActiveMQ:最早使用ActiveMQ,成熟度比较高,但是现在使用比较少,社区也不是很活跃
  • Kafka:适合于大数据的场景,吞吐量很高,属于分布式架构,但性能稳定性相对较差
  • RabbitMQ:单机吞吐量也很高,属于分布式架构,消息可以做到0丢失,但支持的语言不多

2、RabbitMQ的几个概念和几大工作模式?

一些概念

  • Broker: 简单来说就是消息队列服务器
  • Exchange: 消息交换机,它指定消息按什么规则,路由到哪个队列
  • Queue: 消息队列载体,每个消息都会被投入到一个或多个队列
  • Binding: 绑定,它的作用就是把exchange和queue按照路由规则绑定起来
  • Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
  • VHost: vhost 可以理解为虚拟 broker ,即 mini-RabbitMQ server。其内部均含有独立的 queue、exchange 和 binding 等,但最最重要的是,其拥有独立的权限系统,可以做到 vhost 范围的用户控制。当然,从 RabbitMQ 的全局角度,vhost 可以作为不同权限隔离的手段(一个典型的例子就是不同的应用可以跑在不同的 vhost 中)。
  • Producer: 消息生产者,就是投递消息的程序
  • Consumer: 消息消费者,就是接受消息的程序
  • Channel: 消息通道,在客户端的每个连接里,可建立多个channel,每个channel代表一个会话任务
image-20211017194106030

几大工作模式

  • simple模式
img

1.产生消息,将消息放入队列

2.消息的消费者(consumer) 监听 消息队列,如果队列中有消息,就消费掉,消息被拿走后,自动从队列中删除

(隐患:消息可能没有被消费者正确处理,已经从队列中消失了,造成消息的丢失,这里可以设置成手动的ack,但如果设置成手动ack,处理完后要及时发送ack消息给队列,否则会造成内存溢出)。

  • work工作模式
img

消息产生者将消息放入队列消费者可以有多个,消费者1,消费者2同时监听同一个队列,消息被消费。C1 C2共同争抢当前的消息队列内容,谁先拿到谁负责消费消息

(隐患:高并发情况下,默认会产生某一个消息被多个消费者共同使用,可以设置一个开关(synchronized) 保证一条消息只能被一个消费者使用)。

  • publish/subscribe发布订阅模式

img

1、每个消费者监听自己的队列;

2、生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将接收到消息。

  • routing路由模式

img

1.消息生产者将消息发送给交换机按照路由判断,路由是字符串(info) 当前产生的消息携带路由字符(对象的方法),交换机根据路由的key,只能匹配上路由key对应的消息队列,对应的消费者才能消费消息;

2.根据业务功能定义路由字符串

3.从系统的代码逻辑中获取对应的功能字符串,将消息任务扔到对应的队列中。

4.业务场景:error 通知;EXCEPTION;错误通知的功能;传统意义的错误通知;客户通知;利用key路由,可以将程序中的错误封装成消息传入到消息队列中,开发者可以自定义消费者,实时接收错误;

  • topic 主题模式

img

1.星号井号代表通配符

2.星号代表多个单词,井号代表一个单词

3.路由功能添加模糊匹配

4.消息产生者产生消息,把消息交给交换机

5.交换机根据key的规则模糊匹配到对应的队列,由队列的监听消费者接收消息消费


3、简述分代垃圾回收器是怎么工作的?

  • 分代回收器有两个分区:老生代和新生代,新生代默认的空间占比总空间的 1/3,老生代的默认占比是 2/3。

  • 新生代使用的是复制算法,新生代里有 3 个分区:Eden、To Survivor、From Survivor,它们的默认占比是 8:1:1,它的执行流程如下:

    • 把 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)时,升级为老生代。大对象也会直接进入老生代。
  • 老生代当空间占用到达某个值之后就会触发全局垃圾收回,一般使用标记整理的执行算法。以上这些循环往复就构成了整个分代垃圾回收的整体执行流程。


4、什么是类加载器,类加载器有哪些?

实现通过类的权限定名获取该类的二进制字节流的代码块叫做类加载器。

主要有一下四种类加载器:

  • 启动类加载器(Bootstrap ClassLoader)用来加载java核心类库,无法被java程序直接引用。
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
  • 用户自定义类加载器,通过继承 java.lang.ClassLoader类的方式实现。

5、解释Spring支持的几种bean的作用域?

Spring框架支持以下五种bean的作用域:

  • singleton : bean在每个Spring ioc 容器中只有一个实例。
  • prototype:一个bean的定义可以有多个实例。
  • request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • session:在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。
  • global-session:在一个全局的HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。

6、spring 自动装配 bean 有哪些方式?

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。

在Spring框架xml配置中共有5种自动装配:

  • no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean。
  • byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。
  • byType:通过参数的数据类型进行自动装配。
  • constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配。
  • autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

7、请说说MyBatis的工作原理

在学习 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便于理解程序。MyBatis 的工作原理如下图

MyBatis工作原理

1)读取 MyBatis 配置文件:mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。

2)加载映射文件。映射文件即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。

3)构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。

4)创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。

5)Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。

6)MappedStatement 对象:在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。

7)输入参数映射:输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。

8)输出结果映射:输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。

8、Mybatis的一级、二级缓存?

1)一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

2)二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap 存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现Serializable序列化接口(可用来保存对象的状态),可在它的映射文件中配置 ;

3)对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

9、什么是聚簇索引?何时使用聚簇索引与非聚簇索引?

  • 聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
  • 非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行,myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因

innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值。


10、联合索引是什么?为什么需要注意联合索引中的顺序?

MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。

具体原因为:

MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。

当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。


11、什么是MyBatis?

MyBatis是一款轻量级、半ORM持久层框架,可以定义sql、存储过程、映射等。可以使用简单的xml文件对数据库和Java对象进行映射。

ORM:表示对象关系映射,将程序中的对象自动持久化到关系型数据库中。


12、为什么使用MyBatis不用JDBC了?

JDBC的步骤很繁杂,包括6步:注册驱动、获取连接、获取数据库操作对象、执行sql、处理查询结果集、关闭连接。

这6步中和真实查询有关的步骤很少,所以使用MyBatis帮我们简化了操作。

  • 直接使用数据库连接池,使用它建立连接、关闭连接等操作
  • sql语句不再写在代码中了,这样方便解耦
  • 直接建立数据库和java对象的映射,避免了结果集的处理

13、Mybatis优缺点?

  • 优点:
    • MyBatis是基于sql编程的,相当灵活,sql写在xml配置文件中,和java代码分离
    • 简化了JDBC的很多操作,让我们可以关注于业务代码
    • 可以和很多的数据库兼容
    • 可以自动建立ORM关系
    • 可以兼容Spring
  • 缺点:
    • 需要自己手动写sql,可能多表操作的时候会麻烦一点
    • sql依赖于数据库,所以不能轻易更改数据库

14、MyBatis编程步骤是什么样的?

  1. 创建SqlSessionFactory
  2. 通过SqlSessionFactory创建SqlSession
  3. 通过sqlsession执行数据库操作
  4. 调用sqlsession.commit()提交事务
  5. 调用sqlsession.close()关闭会话

15、MyBatis为什么需要预编译?

预编译:数据库驱动在向DBMS中发送sql语句之前,先进行一次编译,这个操作叫预编译

为什么要预编译:对sql进行优化,确保sql语句可执行,把sql语句中的#{}使用占位符替换,这样可以避免sql注入,同理一个sql在与编译后的PrepareStatement对象可以保存在缓存中,如果下次查询的还是这个sql,那么可以直接使用这个PrepareStatement对象。


16、MyBatis有哪些执行器?它们有哪些区别?

有三种执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。

  • SimpleExecutor:简单的执行器,每次执行select或update,都会创建一个新的Statement对象,然后把sql语句放入Statement对象中
  • ReuseExecutor:可重用的执行器,每次执行select或update语句,会以sql作为key来进行查询,如果存在就放入Statement对象,如果不存在那么就创建Statement对象,使用完毕后不关闭,而是保存到Map<String,Statement>中
  • BatchExecutor:批量处理的执行器,只支持update不支持select,每次执行update时,都会把所有sql添加到批处理当中等待统一执行(executeBatch()方法),它缓存了多个Statement对象,每个Statement对象都是等待addBatch()后,等待执行executeBatch()

17、Mybatis是否支持延迟加载?如果支持,它的实现原理是什么?

MyBatis仅支持association关联对象和collection关联对象的延迟加载,association是一对一的关系,collection是一对多的关系,在xml配置文件中使用lazyLoadingEnabled=true/false来设置即可。

可以使用cglib等动态代理的方式来帮助延迟加载,比如a.getB().getName(),会先进入拦截器调用a.getB(),然后当看到a.getB()=null的时候,会调用查询关联B对象的sql语句,然后再使用a.setB()把值赋入,于是a对象的b属性就有值了,这就是延迟加载的方式


18、#{}和${}的区别?

#{}是占位符,采用的是预编译的方式;${}是字符串的替代,采用的是直接赋值的方式。

#{}采用的是PrepareStatement处理sql语句,在预编译时,#{}的位置会替换为?,而 {}采用的**Statement**处理sql语句,是直接替换相应的值;所以**#{}不会有sql注入的问题,\{}存在sql注入问题。**

#{}替换会给对应的变量加上单引号‘ ’,而${}不会加。


19、mapper中如何传递多个参数?

  • 顺序传参
public User selectUser(String name, int deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{0} and dept_id = #{1}
</select>

#{数字}的方式按顺序进行传参,但这种方式一旦变换顺序就查询不到对应的结果

  • @Param注解传参
public User selectUser(@Param("userName") String name, int @Param("deptId") deptId);

<select id="selectUser" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

@Param(参数名)和xml文件的#{参数名}相互对应就可以传参,这种方式比较直观

  • Map传参法
public User selectUser(Map<String, Object> params);

<select id="selectUser" parameterType="java.util.Map" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

可以按照Map<>中放入属性值的方式传递参数

  • Javabean传参
public User selectUser(User user);

<select id="selectUser" parameterType="com.jourwon.pojo.User" resultMap="UserResultMap">
    select * from user
    where user_name = #{userName} and dept_id = #{deptId}
</select>

方法当中传递的是对象,然后让sql中的#{属性名}去匹配这个对象的属性名


20、当实体类中的属性名和表中的字段名不一样 ,怎么办?

  • 通过在查询的SQL语句中定义字段名的别名
<select id="getOrder" parameterType="int" resultType="com.jourwon.pojo.Order">
       select order_id id, order_no orderno ,order_price price form orders where order_id=#{id};
</select>
  • 通过<resultMap>来映射字段名和实体类属性名的一一对应的关系
<select id="getOrder" parameterType="int" resultMap="orderResultMap">
	select * from orders where order_id=#{id}
</select>

<resultMap type="com.jourwon.pojo.Order" id="orderResultMap">
    <!–用id属性来映射主键字段–>
    <id property="id" column="order_id">

    <!–用result属性来映射非主键字段,property为实体类属性名,column为数据库表中的属性–>
    <result property ="orderno" column ="order_no"/>
    <result property="price" column="order_price" />
</reslutMap>

21、什么是MyBatis的接口绑定?有哪些实现方式?

在MyBatis中定义接口,接口里面的方法可以和xml配置文件的sql相匹配,这样直接调用接口方法就可以。

实现方式:

  • 通过注解绑定,就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;

  • 通过xml里面写SQL来绑定, 在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名


22、使用MyBatis的mapper接口调用时有哪些要求?

  • Mapper接口方法名和mapper.xml中定义的每个sql的id相同。(方法名相同)
  • Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。(方法参数相同)
  • Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。(方法返回值类型相同)
  • Mapper.xml文件中的namespace即是mapper接口的类路径。(类路径名相同)

23、Mybatis是如何进行分页的?分页插件的原理是什么?

MyBatis采用RowBounds对象进行分页的,它属于逻辑分页(也就是先查出所有符合要求的结果集放入内存根据条件进行分页)。

分页插件的原理就是根据MyBatis提供的插件接口,设置方法,拦截待处理的sql,然后重写拦截的sql,根据dialect方言,添加分页参数和分页逻辑。

比如拦截前:

select * from user;

拦截后,可能修改sql为:

select * from user limit 0,10;

24、Mybatis的一级、二级缓存?

  • 一级缓存:就是PerpetualCache的HashMap本地缓存,它的作用域是sqlSession。而当执行了增删改操作,会清除当前缓存
<setting name="cacheEnabled" value="true" />
  • 二级缓存:需要手动开启和配置,他是基于namespace级别的缓存(基于mapper层的缓存)。
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>