栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

JDBC事务

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

JDBC事务

1. JDBC访问流程

注册驱动通过DriverManager获取连接获取statement

PreparedStatement是预编译的statement防治SQL注入 给 PreparedStatement设置参数执行插入或者修改或者查询

查询会返回resultSet对象,然后再去解析resultSet对象 2. ThreadLocal底层原理

ThreadLocal的作用
threadLocal代表当前线程,我们可以往当前线程里面绑定相应的值,后面可以通过当前线程获取到这个值

为什么ThreadLocal能够把值绑定到当前线程?
threadLocal会为我们每一个线程去创建一个map集合,当调用set方法时,首先会获取到当前线程的引用,然后获取到当前线程对应的map集合,往map集合当中去设置值;当调用get方法时,会获取到当前线程对应的map值,然后在获取到对应的值

3. 聊聊mybatis

mybatis当中有mapper动态代理开发,实际上当我们编写mapper接口后,mybatis会用接口生成一个代理对象,当mybatis接口对象的任何方法调用之后都会去触发动态代理的invoke方法,如果说我们用了XML,那么会根据XML标签去解析执行到底是插入还是查询等,获取SQL,如果说使用的注解,那么会解析方法上面的注解,得到SQL,实际上底层也是通过JDBC执行SQL

如果是查询,执行完查询之后他会把返回结果用反射进行封装

4. ConcurrentHashMap

HashTable加了锁,当多个线程来操作的时候现在同一时刻只要一个线程进来

1.8之前
ConcurrentHashMap会采用分段锁,16个小段,分别上锁和释放锁;

5. 什么是IOC

把创建对象的交给Spring容器
XML当中配置了Bean标签,Spring底层会去解析XML,解析到Id、class,然后利用反射创建对象放到map容器,key就是id或者name,value就是反射创建的这个对象;获取就直接通过map获取

6. 什么是DI

在创建对象的时候给Bean赋值

在XML当中Bean标签当中配置了property标签,在这个property标签配置了值是哪些,然后Spring底层利用反射去解析XML之后给对应的属性赋值操作

7. Spring整合servlet原理

当每次调用service的时候都会去创建spring容器?怎么解决
spring容器只能创建一次,可以放到ServletContext对象,域对象,整个容器只有一个

当Spring和Servlet整合的时候首先需要在web.xml当中去配置监听器,这个监听器就会去监听到servletContext的创建,因为要创建Spring容器,所以当我们创建Spring容器的时候实际上会要指定Spring配置文件,然后我们会在web.xml当中配置全局的初始化参数去指定Spring配置文件的位置;然后在监听器里面我们可以通过servletContext来获取到配置文件的位置;在监听器当中去创建spring容器,创建完成之后我们需要把spring容器放到servletContext域当中,后面的servlet如果要来使用spring容器,可以直接从servletContext域当中获取到spring容器

8. Spring父子容器

Spring的父容器是配置监听器的并且把它放到ServletContext当中,子容器就是前端控制器,那么他在什么时候创建呢?再DispatcherServlet继承frameworkServlet再继承HttpServletBean里面有个init方法,该方法里面有个initServletBean方法,该方法被frameworkServlet所重写,然后初始化创建子容器initWebApplicationContext,该方法里面首先会获取到父容器的引用,然后创建子容器的时候把父容器传递进去,记住父容器的引用(为了就是可以在子容器当中拿到父容器的bean,可以注入父容器)。

子容器会在监听器之后创建,然后会配置项目启动的时候创建即DispatcherServlet创建的时候创建,如果不配置则默认再第一次访问的时候创建,但是对第一个用户体验太差!

父容器不能注入子容器的bean
子容器可以注入父容器的bean

8. 类加载器

就是将class文件加载到方法区里面class区
启动类加载器

扩展类加载器

系统类加载器

9. 处理器适配器是如何获取参数并封装的

处理器适配器会调用适配器controller当中的方法来执行,首先会通过反射来拿到这个方法,也可以通过反射来获取到参数名称,直接通过request.getParameter来取出参数,在执行方法的时候在通过反射的invoke把参数传递过来

10. 怎么配置注解事务

首先配置事务管理器然后配置开启注解事务然后在需要开启事务的类上加上@Transaction注解实际上还是可以配置传播行为和隔离级别,也可以在方法上配置可以覆盖类上的配置 11. SpringAOP的理解

    aop是一种思想,纵向重复代码横向抽取。Springaop底层采用动态代理来实现的,如果实现了接口spring会采用jdk动态代理,如果没有实现接口会采用cglib动态代理,采用子类继承的方案。

    使用AOP在Spring里面使用首先去编写通知,前置通知、异常通知、环绕通知、最终通知,然后配置切入点表达式,把通知和切入点表达式结合起来配置一个切面,针对需要拦截的方法生成代理对象

    springaop可以控制事务,控制事务有两种方式;第一种是手动控制(编程事务)、第二种AOP自动的声明事务,不管用哪种方式去控制都需要配置事务管理器,因为持久层的技术有各种,所有spring为我们提供了platformtransactionmanager接口,在这个接口里面有获取事务、提交事务、回滚事务的方法

    在开启事务的事务需要传递事务的定义信息TransactionDefinition,这个定义信息的隔离级别是什么,事务的传播行为(决定了事务在业务方法之间如何传递的问题),设置了定义信息就可以获取开启事务会返回事务的状态,在这个事务状态里面封装了是否提交、是否回滚,紧接着就可以拿到commit、rollback回滚的操作了;这样就可以控制细粒度事务,但是它也会造成在每个service方法都会编写这个事务,很麻烦

    此时可以利用AOP的思想环绕通知,我们在执行业务代码之前来开启事务,执行完了之后就提交事务,异常那么在catch里面回滚,这样就利用了AOP的思想把事务封装起来了

    spring提供了两种声明式事务,第一种使用XML方式,首先配置事务管理器,第二配置事务的通知(attributes属性)去设置这些方法,哪些方法开头的,隔离界别、是否只读、传播行为等,然后配置织入(通知和被代理对象结合起来创建代理对象的过程,配置切入点表达式,配置切面)

    第二种还可以配置注解的方式,第一配置事务管理器,第二是开启事务注解驱动,接下来我们只需要在类上标志Transaction注解就可以,如果我们在方法上配置了那么就可以覆盖在类上的配置

编程式事务是细粒度的事务,Spring其实也为我们提供了一个工具类叫做TransactionTemplate,该工具类要注入事务管理器程序员需要在业务方法里面去注入这个工具类,然后调用excute方法,里面传入一个回调的方法,因为Spring不知道我们具体要调用什么方法,因此采用了回调的思想在这个excute方法的底层首先会是开启事务、执行业务代码、提交事务、回滚事务等在这个excute传入的回调方法可以是有返回值的,也可以是没有返回值的没有返回值的这个类是一个抽象类,它实现了有返回的这个接口,重写了doTransaction的这个方法,并且调用了自己没有返回的这个抽象方法,并且这个抽象方法是被我们程序员所重写这个过程就行浏览器发送请求到tomcat,调用了service方法,service方法会调用get或者post等方法 12. 为什么二级缓存和三级换存是HashMap

因为二级缓存和三级缓存是成对出现的,集二级缓存里面是三级缓存生成的,所有两个map里面不能同时存在bean,所有应该保证原子性,就算他们是concurrentHashMap也得加锁,所有反正都要加锁,就没有必要是concurrentHashMap了

13.spring怎么实例化bean的

spring实例化bean有声明周期,首先实例化对象,然后推断构造方法,拿到构造方法反射创建出来

14. 如何解决循环依赖的

spring进行扫描-反射后封装成beanDefinition对象-放入beanDefinitionMap-遍历map-验证(是否单例、是否延迟加载、是否抽象)-推断构造方法-准备开始进行实例-去单例池中查,没有-去二级缓存中找,没有提前暴露-生成一个objectFactory对象暴露到二级缓存中-属性注入,发现依赖Y-此时Y开始它的生命周期直到属性注入,发现依赖X-X又走一遍生命周期,当走到去二级缓存中找的时候找到了-往Y中注入X的objectFactory对象-完成循环依赖。

15. MySQL执行流程

from
on
join
where
group by
having
select
distinct
union
order by

16. 分布式Session问题

nginx对web服务器进行了集群,但是登陆状态存储在服务器进程内,nginx转发到不同的服务器,造成了重复的登陆问题

17. 如何解决分布式Session

为了保证每一次客户端都是访问都是同一个服务器,ip hash的负载均衡策略;缺点:这台服务器宕机了,这个客户的登陆信息就没有了tomcat可以设置session共享;缺点:每台服务器都有登陆的信息数据有冗余其次就是只能是tomcat服务器之间,服务器不能混用了 18. 正向代理

客户端行为,例如浏览器要访问某某网站,因为防火墙的原因访问不了,因此需要使用代理服务器,然后代理服务器帮我们访问,访问结果转发给客户端

也可以是客户端为了加速某某网站,然后配置了代理服务器来加快它访问,实际上对应游戏网站根本就不知道使用了代理服务器

19. 反向代理

服务器行为,nginx处理不了动态资源的请求,然后nginx把请求转发给tomcat,处理完毕指挥相应给nginx,再由nginx相应给浏览器,浏览器也不知道是访问的是tomcat,以为是访问的nginx

20. JWT

客户端发起登陆请求,服务器端生成JWT令牌,首先服务端这边有个secret密钥,然后服务端首先会把头部生成加密算法使用base64进行编码,载荷存放登陆信息以及权限的信息,也会使用base64进行编码,紧接着会把(头部base64+载荷base64+密钥)在通过头部声明的加密算法进行加密就生成签名,然后把JWT办法给客户端,客户端后面会把JWT携带给服务器,

服务器在验签的时候:

首先会获取JWT令牌服务器获取头部和载荷,并解析头部声明的加密算法服务器会使用(头部base64+载荷base64+密钥)在用头部声明的加密算法再次进行加密,然后把加密后的信息和签名的信息进行比较,如果一致代表签名合法,没有被篡改,如果不一致,不认一致则获取载荷当中的数据,载荷数据是合法的,没有被篡改

验证过程
服务器端会编写一个拦截器主要是判断客户端是否发送了token以及判断token是否是合法的,如果是合法的就会获取载荷当中的内容并且绑定用户信息到当前的线程,当访问后端的接口的时候会从当前线程当中获取用户权限的列表,

21. Java的可见性

首先Java的内存模型当中有主内存,每个线程都有本地内存,本地内存会拷贝主内存的副本,当线程执行的时候实际上是改变本地内存的值,本地内存改了之后会刷新到主内存,另外一个线程会到主内存当中读取到值过后更新到本地内存,相互之间通过主内存来通讯

如果加了volatile关键字会解决可见性的问题,因为可以确保本地内存的值改变了之后会即使的刷新到主内存当中,另外一个线程可以及时的去读取数据

重排序
计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排,volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象

22. 阻塞队列和非阻塞队列

阻塞队列和非阻塞队列都实现了队列的结构

阻塞队列:因为队列有容量所以在入队的时候,可以设置阻塞的时间,如果队列满了,就阻塞,如果有位置就放置进去,出队的时候如果队列是空的也可以进行阻塞的状态

非阻塞队列:代表高性能的队列,不用设置队列的大小可以直接放置进去,不会阻塞

23. 线程池工作原理

首先会判断线程池当中的线程数是否小于核心池设置的大小如果小则会创建一个新的线程来执行,如果这个线程执行完成任务,不会关闭,他会尝试从阻塞队列当中获取新的任务,如果没有任务则进入阻塞状态如果三个线程都在忙碌,然后又有新的任务,创建新的线程数量以及超过核心池的大小,不会再去创建新的线程,把这个任务放到阻塞队列如果现在有其他的任务,仍然继续往阻塞队列当中放任务,如果放的时候阻塞队列满了,这个时候如果我们配置的最大线程数大于核心线程池的大小,就会创建新的线程执行任务这个新的线程执行完毕之后不会立即关闭,他也会尝试从阻塞队列当中获取任务来执行如果超过了设置时间还没有新的任务来执行,那么这些额外创建的线程就会全部关闭如果所有的线程都在执行任务,并且达到了最大线程数,并且阻塞队列也满了,这个时候会执行拒绝策略

24. Callable

编写类继承Thread类编写了实现Runable接口编写类实现Callable接口

代表当前任务带有返回的结果而且Callable必须交给线程池来执行,交给线程池之后,会在线程池执行完成之后才会获取到结果因此会返回Future对象,代表会在未来才能获取到结果现在在创建线程池的主线程里面通过Future.get()是个阻塞的方法阻塞的方法有两种情况:

执行玩有结果之后直接获取到结果没有结果则进行阻塞状态

接着聊线程池的核心配置参数、线程池底层原理,拒绝策略,自定义拒绝策略、合理配置线程池

25. 什么是垃圾回收机制

jvm会不定时的回收不可达的对象

什么是不可达的对象
对象没有被引用或者没有存活

垃圾回收机制是Java的核心,也是必不可少的,Java有一套自己的垃圾清理机制,开发人员无需手动的清理。

26. 堆内存

堆内存分为新生代和老年代,新生代占1/3,老年代占2/3

新生代分为Eden区和s0和s1区
Eden占8/10,两个survivor各占1/10

27. 如何判断对象是否存活

引用计数法

默认年龄为0岁,每个对象都有一个年龄,如果小于或者等于15岁,存放在新生代当中,如果大于15岁就会存放到老年代当中

GC线程会不定时的回收,如果判断对象被引用的话则+1岁,如果没有就-1岁
如果年龄为0岁,会被垃圾回收机制认为是个不可达的对象会被回收

循环引用问题

会造成内存泄漏
根搜索算法
什么是根

(1). 虚拟机栈(栈帧中的局部变量区,也叫做局部变量表)中引用的对象。
(2). 方法区中的类静态属性引用的对象。
(3). 方法区中常量引用的对象。
(4). 本地方法栈中JNI(Native方法)引用的对象。

28. 复制算法

Eden里面的对象用可达性分析算法发现存活则放入幸存区,然后幸存区满了又用可达性分析算法标记哪些是存活的哪些是没有存活的,会把存活的对象复制到另外一个幸存区里面,把刚才那个幸存区直接清理掉

适用于新生代

优点:可以解决内存碎片化问题、快速、清理干净
缺点:浪费内存

s0和s1一定有一个为空,因为为了下一次的复制

29. 标记整理算法

适用于老年代

优点:解决内存碎片化问题
缺点:更新引用

30. 标记清除算法

发生在老年代
一个是标记,另一个就是清除。标记就是根据特定的算法(如:引用计数算法,可达性分析算法等)标出内存中哪些对象可以回收,哪些对象还要继续用。标记指示回收,那就直接收掉;标记指示对象还能用,那就原地不动留下。

缺点:产生内存碎片化问题,因为内存不够连贯

31. Minor GC和FullGC区别

Minor GC:指发生在新生代的垃圾回收动作,因为Java对象大多都具备朝生夕灭的特性,一般回收机制也比较快

MajorGC:指发生在老年代的GC,出现了MajorGC经常会伴随至少一次的MinorGC

31. 永久代

JDK1.8的时候把永久代替换为元空间是直接使用本地内存(方法区里面的东西直接使用本地内存,不在走JVM来分配了)

如果JVM分配了500M空间,JDK1.8之前永久代可能只能分配100M
现在JVM分配了500M空间,现在的元空间可以直接使用物理内存

之前给JVM分配空间可能预估不足,永久代可能会产生内存溢出

元空间和永久代区别元空间和永久代类似,但是元空间并不在虚拟机内,而使用本地内存,理论上取决于32位/64位操作系统的大小

在1.8之前方法区放到永久代里面,1.8之后把永久代去除了,方法区

32. 内存溢出和内存泄露区别

Java内存泄漏就是没有及时清理内存垃圾,导致系统无法再给你提供内存资源(内存资源耗尽);

而Java内存溢出就是你要求分配的内存超出了系统能给你的,系统不能满足需求,于是产生溢出。

内存溢出,这个好理解,说明存储空间不够大。就像倒水倒多了,从杯子上面溢出了来了一样。

内存泄漏,原理是,使用过的内存空间没有被及时释放,长时间占用内存,最终导致内存空间不足,而出现内存泄漏。

33. 引用

强引用
以前我们使用的大部分引用实际上都是强引用,这是使用最普遍的引用。如果一个对象具有强引用,那就类似于必不可少的生活用品,垃圾回收器绝不会回收它。当内存空间不足,Java虚拟机宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会靠随意回收具有强引用的对象来解决内存不足问题。

弱引用
就类似于生活当中可有可无的商品,与软引用相比,在垃圾回收的时候,一旦发现弱引用的对象,不管内存空间足够与否,都会回收它的内存;由于垃圾回收器是优先级很低的线程,因此不一定会很快的发现弱引用对象

弱引用可以和引用队列结合使用,如果弱引用被垃圾回收器回收,Java虚拟机会把这个弱引用加入到与之关联的引用队列当中

34. ThreadLocal内存泄漏

ThreadLocal的key是弱引用,key为使用弱引用的ThreadLocal实例,value为线程变量的副本。弱引用不管内存是否够都会回收,但是value是强引用,而这个value会在调研set(),get(),remove()清除掉,因此避免内存泄漏问题,虽然这个key是弱引用但是只是多了一层屏障,为了确保value被删除,因此每次使用完ThreadLocal之后我们要调用remove()方法

tomcat的底层是线程池,也就以为线程不会关闭,当有一个请求过来之后会拿一个线程来执行,然后这个线程会对于有个threadLocalMap,并且使用了当前线程ThreadLocal,我们往里面绑定一个用户,然后在过滤器里面,我们一定要调用remove方法,这样才能让value清除

ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有thead线程退出以后,value的强引用链条才会断掉。

但如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:

Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

永远无法回收,造成内存泄漏。

那为什么使用弱引用而不是强引用??

我们看看Key使用的

key 使用强引用

当ThreadLocalMap的key为强引用回收ThreadLocal时,因为ThreadLocalMap还持有ThreadLocal的强引用,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。

key 使用弱引用

当ThreadLocalMap的key为弱引用回收ThreadLocal时,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次ThreadLocalMap调用set(),get(),remove()方法的时候会被清除value值。

35. 垃圾回收器

串行
只有一个线程,执行垃圾回收时程序停止的时间比较长,垃圾回收时,只有一个线程在工作, 并且java应用中的所有线程都要暂停,等待垃圾回收的完成,作用于新生代,采用复制的垃圾回收算法

并行
线程执行垃圾回收适合于吞吐量的系统,回收时系统会停止运行,就是把单线程改为多线程的垃圾回收,作用于新生代,采用复制的垃圾回收算法,JDK8默认使用的垃圾回收器

parNew就是Serial的多线程版本,新生代并行,老年代串行,老年代使用的是标记压缩算法

CMS
是一款并发的标记清除算法垃圾回收器,该回收器针对老年代,是一款为了获取最短回收停顿时间的垃圾回收器,停顿时间短,在进行垃圾回收的同时,系统仍然能运行

初始标记:仅仅标记GCRoots能直接关联到的对象,速度快并发标记:追踪引用链的过程,可以和用户线程并发的执行重新标记:修正并发标记阶段用户线程继续运行而导致标记发生并发清除:清除标记可以回收的对象,可以和用户线程并发执行

由于整个过程耗时最长的并发标记和并发清除都可以和用户线程一起工作,所以总体上来看,CMS收集器的内存回收过程和用户线程是并发执行的。

缺点:

无法处理浮动垃圾:垃圾回收算法导致内存碎片: 35. G1

之前不管串行回收还是并行回收,新生代和老年代会使用两种收集器,
G1把调优的参数给简化了,只需要设置开启G1设置,取消了物理划分,只是逻辑上的划分,有Eden,Survivor,Old,还要巨大对象区域,这个区域会动态划分,不要设置新生代和老年代的大小,G1收集器在运行的时候会调整新生代和老年代的大小
我们只需要设置最大的堆内存大小,紧接着我们只需要调试程序的停顿时间即可

36. JVM参数调优

设置堆的初始大小和最大大小,通常把最大、初始大小设置为相同的值。减少GC的次数

-Xms:设置堆的初始化大小-Xmx:设置堆的最大大小 设置年轻代中Eden区和两个Survivor区的大小比例。该值如果不设置,则默认比例为8:1:1。Java官方通过增大Eden区的大小,来减少YGC发生的次数

-XX:SurvivorRatio 年轻代和老年代默认比例为1:2。可以通过调整二者空间大小比率来设置两者的大小。-XX:newSize 设置年轻代的初始大小-XX:MaxNewSize 设置年轻代的最大大小, 初始大小和最大大小两个值通常相同一般一天超过一次FullGC就是有问题,首先通过工具查看是否出现内存泄露,如果出现内存泄露则调整代码,没有的话则调整JVM参数系统CPU持续飙高的话,首先先排查代码问题,如果代码没问题,则咨询运维或者云服务器供应商如果数据查询性能很低下的话,如果系统并发量并没有多少,则应更加关注数据库的相关问题如果服务器配置还不错,JDK8开始尽量使用G1或者新生代和老年代组合使用并行垃圾回收器 36. Tomcat调优

如果是原生的tomcat我们只需要在catalina设置加上JVM参数即可,在server.xml当中配置线程池的参数

如果是springboot项目然后在使用-jar 的时候配置JVM参数,在yaml或者properties当中配置线程池的参数

37. 数据库三大范式

确保每列保证原子性(每一列只要单一的值不能够再拆分)确保每列的值都与主键相关每一个表都不包含其他表已经包含的非主键信息 37. 数据库使用优化思路

应急调优的思路:

针对突然的业务办理卡顿,无法进行正常的业务处理!需要立马解决的场景!

    show processlist(查看连接session状态)查看是否连接过多explain(分析查询计划),show index from tableName(分析索引)show status like ‘%lock%’; 查询锁状态

常规调优的思路:

针对业务周期性的卡顿,例如在每天10-11点业务特别慢,但是还能够使用,过了这段时间就好了。

    开启慢查询日志,运行一天;(set global slow_query_log=1;)设置慢查询日志的超时时间 (set global long_query_time=1;)查看slowlog,分析slowlog,分析出查询慢的语句。按照一定优先级,进行一个一个的排查所有慢语句。分析top sql,进行explain调试,查看语句执行时间。调整索引或语句本身。
38. 复合索引在什么情况下失效

复合索引是要遵守最左匹配原则,如果现在有name,age,address 那么他会根据name建个索引,根据name,age建个,根据name,age,address建个,他不会用后面两个字段来单独建个索引

39. B树

B树的一个节点可以用于超过2个子节点的二叉查找树,并且一个节点可以存储多个值,那么B树就可以相对于平衡二叉树降低树的高度,那么就可以减少磁盘的IO操作

缺点:仍然需要回旋

40. B+树

解决的范围查找的问题、减少IO查询的操作

用B树的特点降低了树的高度,减少IO查询的操作
非叶子节点只有key,是用来帮我们查找叶子节点的,并且叶子节点会使用链表来连接起来也就是方便我们来范围查找

缺点就是有冗余的数据占用内存

41. 索引的面试题

如果现在使用hash的数据结构来实现索引,这个时候我们可以针对某个字段计算出哈希值得到下标就找到对应的值,缺点就是不支持范围查找,因为底层数据结构是散列的,无法进行比较大小

平衡二叉树的话是右边比它大左边比它小,可以降低磁盘IO的次数,但是平衡二叉树的树的高度比较高,一个节点最多只有两个子节点,并且如果是范围查找它的回旋效率比较低

B树一个节点可以用于超过2个子节点的二叉查找树,而且一个节点可以有多个值,所以树的高度变低了,但是缺点是如果是范围查找仍然是回旋效率比较低

B+树是在B树的特点上多了叶子节点,通过非叶子节点找到叶子节点,非叶子节点只有key没有value,而叶子节点既有key也有value,因为叶子节点会通过链表连接起来,这是可以通过链表很方便的实现范围查找,并且也有B树的特点降低了树的高度

42. SQL优化技巧

使用group by 分组查询是,默认分组后,还会排序,可能会降低速度,在group by 后面增加 order by null 就可以防止排序.有些情况下,可以使用连接来替代子查询。因为使用join,MySQL不需要在内存中创建临时表。对查询进行优化,要尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描唯一性太差的字段不适合单独创建索引,即使频繁频繁更新字段,也不要定义索引。 43. 聚簇索引和非聚簇索引区别

索引的底层数据结果是B+树,那么它的非叶子节点只有key,而叶子节点存储key和value

聚簇索引也成为主键索引,实际上对应的叶子节点上key对应的是主键id,值是主键对应这一行的数据
非聚簇索引也成为二级索引,他在叶子节点上存储的只是主键的id,所以如果是通过非聚簇索引来查询的话首先是在它的叶子节点上招待主键的id,然后通过id到主键索引的叶子节点上找到对应这一行的数据

44. synchronized的底层原理

它是一个JVM级别的锁,是自动上锁和解锁,首先每一个对象关联一个monitor监视的对象,然后它在底层加锁的时候底层有一个monitorenter指令加锁,monitorexit解锁,当在monitorenter进行加锁的时候,多个线程会争先恐后的去monitor对象里面去设置值,如果能从默认值0改为1那么就代表获取到这个锁资源,另外的线程就会陷入阻塞状态,直到执行完成之后把1改为0,其他线程又可以去设置值

如果出现的锁嵌套就是可重入锁,那么这个监视器就会从1加到2加到3等等,里面的锁执行完毕之后会依次的去减为0

45. 为什么在controller当中成员变量可以注入request对象

浏览器每次发送请求到服务器都会创建request对象,而controller要交给springmvc容器来管理,所以当springmvc初始化的时候就会被创建,当在成员变量来注入request对象是有些问题,因为controller是单例的,request每次访问都会被创建,解决就是在controller当中的成员变量创建的是代理对象,在每次访问过来的时候tomcat会创建一个代表请求的request对象和代表响应的response对象,传递service方法springmvc会把request绑定到当前线程!!!;成员变量就是从当前线程获取到的相应的数据

46. ThreadLocal作用

threadLocal可以把当前的请求绑定到当前线程 例如我们可以在controller成员变量的位置注入request,为什么没问题 controller提前就创建好了,而request每次请求都会创建,因此成员变量创建的是一个代理对象,每次请求过来的时候会把request绑定到当前线程,而controller成员变量的request会从当前线程当中去获取request

47. FactoryBean和BeanFactory的区别

ApplicationContext继承了BeanFactory接口,ApplicationContext包含了BeanFactory的所有功能,同时还进行了更多扩展

FactoryBean是个工厂bean,使用了简单工厂模式,是一个可以生产对象的工厂bean 由spring管理后,spring自动调用getObject方法获取具体需要创建的对象交给spring管理

FactoryBean适合于整合第三方框架的时候,因为第三方框架可能会交给Spring容器来进行管理,并且创建第三方框架的时候创建过程十分的繁琐,这个时候第三方框架可以专门写一个FactoryBean实现FactoryBean接口,例如mybatis当中的sqlsessionFactoryBean来交给spring容器来管理,spring会自动的调用getObject方法把创建的对象交给spring容器来管理

如果想要将动态代理创建的对象交给spring容器管理,那么就需要实现FactoryBean接口,把动态代理创建的对象放到getObject方法里面,例如Mapper只有接口没有实现类,然后把这个工厂Bean交给Spring容器来管理,spring会自动调用getObject方法把代理对象放到spring容器里面

BeanFactory是个bean工厂,是一个spring工厂的顶级接口,就是个ioc容器 管理bean的

public interface UserMapper {
	public void hello();
}
@Component
public class UserMapperFactoryBean implements FactoryBean {
    @Override
    public UserMapper getObject() throws Exception {
        Class[] clazz={UserMapper.class};
        return (UserMapper) Proxy.newProxyInstance(UserMapperFactoryBean.class.getClassLoader(), clazz, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("invoke被调用了");
                if(method.getName().equals("hello")){
                    System.out.println("奥利给你好...");
                    return null;
                }else if(method.getName().equals("toString")){
                    System.out.println("toString被调用了...");
                    return "toString xxx";
                }else{
                    return null;
                }

            }
        });
    }

    @Override
    public Class getObjectType() {
        return UserMapper.class;
    }

    @Override
    public boolean isSingleton() {
        return true;
    }
}


"null"是idea会自动的去调用代理对象的toString方法

现在有一个问题?不可能每次创建一个mapper就要对应的去创建一个FactoryBean,因此需要修改


增加了一个有参的构造方法,与此同时也要相应的在bean的定义信息加上有参的参数,

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ChenApplication.class);

AbstractBeanDefinition abstractBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
abstractBeanDefinition.setBeanClass(ChenFactoryBean.class);
abstractBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
applicationContext.registerBeanDefinition("user",abstractBeanDefinition);
applicationContext.refresh();

但是不可能写在启动类当中,因此需要实现importBeanDefinitionRegistrar然后再启动类当中导入该接口,spring才能知道调用这个方法

但是对应mybatis的作者来说,他并不知道我们有哪些mapper,所以不能写死,因此需要扫描路径
可以参照Spring的扫描,但是Spring在接口当中添加@Component是不起作用的,因为不可能实例化接口,但是对应mybatis来说,是只要接口的,与spring的逻辑正好相反

继承spring的扫描的逻辑

传入这个package,返回加了@Component类的个数
在spring扫描的逻辑是当他扫描之后其实会根据这个类生成BeanDefinition


重写了spring的逻辑,把接口作为候选的Component

47. BeanDefinitionBuilder

核心代码:

AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(ChenApplication.class);
applicationContext.refresh();
AbstractBeanDefinition abstractBeanDefinition = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
abstractBeanDefinition.setBeanClass(ChenFactoryBean.class);
abstractBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(UserMapper.class);
applicationContext.registerBeanDefinition("user",abstractBeanDefinition);

user是获取的getObject方法的返回
&user是获取的是LuBanBeanFactory的Bean

48. @EnableAutoConfiguration

它的下面有两个注解,一个是@AutoConfigurationPackage自动扫描包,利用Registrar给容器中导入一系列组件将指定的一个包下的所有的组件导入进来、主程序所在的包下面。

还有就是@import(AutoConfigurationimportSelector.class)

利用getAutoConfigurationEntry(annotationmetadata);给容器中批量导入一些组件调用List configurations = getCandidateConfigurations利用工厂加载 Map loadSpringFactories从meta-INF/spring.factories位置来加载一个文件。

默认扫描我们当前系统里面所有meta-INF/spring.factories位置的文件


虽然我们127个场景的所有自动配置启动的时候默认全部加载,按照条件装配规则(@Conditional),最终会按需配置。 49. SpringBoot自动装配得过程

SpringBoot在启动得时候会自动加载自动装配得jar包,autoconfigure.jar然后加载这个jar包下meta-INF文件下得spring.factories文件读取这个配置文件配置了哪些自动装配的类,然后这个时候会自动加载这个配置文件中的类每一个自动装配的类上都有一个自动装配的注解,这个注解叫做ConditionalOnClass,检测当前项目是否导入了对应的starter,检测当前的自动装配类所需要的这些类在当前项目是否存在,如果存在自动装配就会生效我们这个配置类上还要configuration注解,代表当前类是个配置类,可以写Java配置 50. Innodb和MyISAM的区别

Innodb

innodb存储引擎提供了提交、回滚、崩溃恢复能力的事务安全提拱了对数据库事务ACID的支持,实现了四种隔离级别InnoDB引擎是行锁,粒度更小,所以写操作不会锁定全表,在并发较高时,使用InnoDB会提升效率。即存在大量UPDATE/INSERT操作时,效率较高

使用场景

经常UPDETE/INSERT的表支持事务只能选择innodb可以从灾难中恢复外键约束、列属性AUTO_INCREMENT支持

MyISAM

不支持事务、不支持外键每个MyISAM在存储成3个文件

frm:表结构等信息MYD:存储数据MYI:存储索引

使用场景

经常SELECT的表,插入不频繁,查询非常频繁不支持事务 51. DCL单例模式

双端检索机制不一定线程安全,因为可能会出现指令重排的情况,加入volatile可以禁止指令重排
原因在于当某一个线程执行到第一次检测,读取到instance不为null,instance的引用对象可能没有初始化完成
instance=new SingleDemo()分为三步

开辟内存初始化引用 instance!=nill
步骤二和步骤三不存在依赖关系,所有存在指令重排变为步骤1,3,2
但是得到对象还没有初始化完成最后得到null

第一个if语句,用来确认调用getInstance()时instance是否为空,如果不为空即已经创建,则直接返回,如果为空,那么就需要创建实例,于是进入synchronized同步块。

synchronized加类锁,确保同时只有一个线程能进入,进入以后进行第二次判断,是因为,

对于首个拿锁者,它的时段instance肯定为null,那么进入new Singleton()对象创建,

而在首个拿锁者的创建对象期间,可能有其他线程同步调用getInstance(),那么它们也会通过if进入到同步块试图拿锁然后阻塞。

这样的话,当首个拿锁者完成了对象创建,之后的线程都不会通过第一个if了,而这期间阻塞的线程开始唤醒,它们则需要靠第二个if语句来避免再次创建对象。

52. 什么是线程池

因为不可能每次请求过来的时候都来创建线程,这就会导致性能不高。不管用tomcat还是jetty都是在多线程的环境,那么就会维护一个线程池,提前把线程创建好,当请求来的时候就拿一个线程,当线程执行完之后把线程还到线程池当中

降低资源的消耗提高响应的速度提高线程的可管理性 53. 线程池的核心类

核心池大小、最大线程池大小、最大等待时间、最大等待时间单位、拒绝策略

53. 线程池的工作原理

首先判断线程池的线程数是否小于核心池设置的大小如果小的话会创建一个新的线程来执行,如果这个线程执行完任务不会关闭,他会尝试从阻塞队列当中获取新的任务来执行,如果没有任务则会进入阻塞的状态如果核心线程数都在忙碌,然后又有新的任务,创建新的线程数量已经超过了核心池的大小,不会再去创建新的任务,把这个任务放入阻塞队列如果现在有其他的任务,仍然继续往阻塞队列当中放任务,如果阻塞队列满了,这个时候如果我们配置了最大线程数大于核心池的大小,就会创建新的线程来执行任务这个新的任务执行完成之后不会马上关闭,他会尝试从阻塞队列当中获取任务来执行如果超过了设置时间还没有新的任务来执行,那么这些额外创建的线程就会关闭如果所有的线程都在执行任务,并且达到了最大线程数,并且阻塞队列也满了就会执行拒绝策略 53. 如果线程池内存队列满了会怎样

如果使用的是有界队列还可以创建额外的线程数来执行,但是如果超过了最大线程数则会执行拒绝策略

如果现在有这个需求,因为默认的拒绝策略都抛异常、以及丢弃新的,或者是老的,但是我们希望不希望丢弃而是并发量不高的时候来执行

我们首先可以自定义拒绝策略,把任务持久化到硬盘或者是数据库当中,等空闲的时候从磁盘当中或者是数据库当中读取来执行(使用定时器)

53. 如何自定义拒绝策略

我们只需要编写一个类来实现RejectedExecutionHandler接口

53. 如果线上的机器突然宕机,挤压在队列当中里的任务怎么办

提交任务当线程池之前,我们可以往数据库当中插入这个任务的信息,可以给一个字段,记录状态,未提交、已提交、已完成,之后执行完了过后改变状态就可以了

系统重启,启动线程读取数据库中未完成的任务,然后再把任务重新提交到线程池中去执行。

54. 自定义数据绑定

在springmvc的底层运用了ServletModelAttributeMethodProcessor 这个参数处理器来进行封装,他在他的底层运用了WebDataBinder数据绑定器,这个数据绑定器会将请求的所有数据跟指定的JavaBean进行绑定,如何进行绑定的?这个绑定器里边有ConversionService类型转换服务,在里边注册了非常多的converters,这些converters能将String转成各种的类型,例如这些converters将String转为integer,再将integer转到JavaBean当中,最终完成JavaBean的数据跟请求的数据进行绑定

55. 线程当中如何保证线程安全的问题

使用Synchronized或者Lock锁或者是JUC原子类CAS无锁机制

56. 数据库的优化

可能会使用反三范式来设计设计方法来设计,就可以适当的添加冗余字段来增加查询的效率

57. servlet的生命周期

servlet是跑在Tomcat服务器上的
Tomcat服务器创建了servlet容器

当第一次访问的时候

创建了servlet对象调用了init方法 初始化servlet调用了service方法 处理请求

当我们停止了服务器就调用了destory销毁方法

58. JDK动态代理

因为创建代理对象的时候我们传入了接口,所以我们的代理对象会具备相同的方法,但是JDK动态代理并不知道代理对象要干什么事情,因此只要程序员自己来写所以编写一个类来实现InvocationHandler接口,当代理对象的方法被调用之后都会触发invoke方法

59. 本地方法栈

Java的底层JVM基本都是C或者是C++写的,不管本地方法是什么实现的总归有些代码是要执行的,那么就需要内存空间,那么就分配到本地方法栈当中

60. 栈

当主线程来运行main方法,Java虚拟机会给这个线程分配个专属的线程栈内存空间,只要运行到方法就会在他自己线程栈上给这个方法分配个内存空间用来存放方法内部的局部变量,这块内存就叫栈帧方法内存空间

61. 程序计数器

记住下一条JVM内存的地址

62. 方法区内存

被虚拟机加载的类信息、常量、静态常量等,是线程共享的
运行时常量池也是方法区的一部分,Class文件除了字段、方法、版本接口等描述信息还有一项就是常量池,用于存放编译期间生成的各种字面量的符合的引用,这部分内容将在加载后进入方法区的运行时常量池中存放。

61. 远程调用过程

去创建一个service接口,给这个service生成代理对象,然后当调用接口的方法的时候会触发invoke方法,在invoke方法里面发起socket请求,去请求服务的提供方,让服务的提供方那边去执行本地执行,执行完之后把结果返回给调用方

62. IO和NIO的区别

dobbo的底层用到了netty,netty的底层用到了NIO,dubbo的底层不用传统的IO而用的是NIO,因为传统的IO是一个线程只能处理一个socket的请求,性能比较低而且是一个阻塞的IO,NIO是一个非阻塞的IO,可以实现一个线程去处理多个socket的请求并且是带有缓存的还要选择器,性能比较高

63. Mongodb的使用场景

MongoDb 可以用来存储一些不重要的数据,并且不能够设计到复杂的表操作,因为他是不支持事务的

物流场景,使用 MongoDB 存储订单信息,订单状态在运送过程中会不断更新,以 MongoDB 内嵌数组的形式来存储,一次查询就能将订单所有的变更读取出来。视频直播,使用 MongoDB 存储用户信息、礼物信息等 64. zk的监听器

zk是有监听器的,它允许某个客户端针对某个子节点的变化进行监听,不管是新增还是减少子节点的数据的变化都能够进行相应的监听,并且在监听的时候使用的是长连接

如果创建临时节点的客户端断开与zk的连接,那么这个临时节点超时过后会自动的删除

65. HttpClient实现RPC

浏览器访问服务器的过程

打开浏览器输入网址访问结果

使用HttpClient访问WEB服务的过程

创建客户端,相当于打开浏览器创建请求地址,相当于输入网址发起请求,相当于访问网站处理相应结果,相当于浏览器显示结果

public class TestHttpClient {
    public static void main(String[] args) throws IOException {
        testGetNoParams();
    }

    
    public static void testGetNoParams() throws IOException {
        // 创建客户端对象
        HttpClient client = HttpClients.createDefault();
        // 创建请求地址
        HttpGet httpGet = new HttpGet("http://localhost/test");
        // 发送请求 ,接收相应对象
        HttpResponse response = client.execute(httpGet);
        // 响应体和响应头,都是封装的HTTP协议数据。直接使用可能有乱码或解析错误
        HttpEntity entity = response.getEntity();
        // 通过HTTP实体工具类,转换响应体数据。使用的字符集是UTF-8
        String responseString = EntityUtils.toString(entity,"UTF-8");
        System.out.println("服务器相应数据:"+responseString);
        client=null;
    }
}

有参数的POST传递参数

66 .HTTP协议和RPC协议对比

RPC适用于公司的内部服务调用,传输效率高,性能消耗低

1、传输协议

RPC:可以基于HTTP协议,也可以基于TCP协议HTTP:基于HTTP协议

2、性能消耗

RPC:可以基于thrift实现高效的二进制传输HTTP:大部分是基于json实现的,字节大小和序列化耗时都比thrift要更消耗性能

3、负载均衡

RPC:基本自带了负载均衡策略HTTP:需要配置Nginx、HAProxy配置 67. CPU100%的排查方案

首先使用top -c 查看CPU的使用率进一步使用top -Hp 14724定位该进程内所有的线程使用情况将查出的线程id转换为16进制aa然后用jstack查出该线程具体的堆栈 68. Redis和memcached的区别

现在一般都会采用redis来实现缓存存储

reids支持更丰富的数据类型,不仅仅支持string还支持list、set、hash、zset等,而memcached只支持string数据类型redis支持数据的持久化,可以将内存的数据持久化保存到磁盘当中,而memcached将数据保存到内存当中,断电及失memcached是多线程,非阻塞IO复用的网络模型,而redis是单线程的多路IO复用模型 69. 缓存的双写不一致的问题

我们项目分为mobile和管理端admin,我们在mobile端做了缓存,但是需要解决缓存的双写不一致问题。比如酒店详情缓存,我们在修改了酒店详情缓存之后,mobile缓存的还是老的数据,出现了缓存不一致的问题
我们可以在admin管理端修改酒店的时候发送一个HttpClient请求来删除mobile端的缓存当中的数据

现在有个问题
在项目当中是在用户访问的时候做的缓存,假设系统很多酒店,在项目启动的时候,有许多请求访问不同的酒店那么就都会从缓存当中找,没有,再到数据库,从而打垮数据库,因此在启动项目的时候把缓存做好

70. redis如何解决分布式session的问题

因为现在做了集群的部署,而session是保存到内存当中的,集群项目的的内存是独立的,造成重复登陆的问题

如果本来是传统的项目改动不想太大

那么就可以使用nginx负载均衡策略IP绑定,这一台服务器的用户信息可能丢失tomcat的session共享,会造成数据冗余,而且tomcat和jetty不能混用共享Jwt是在客户端解决的,当登陆之后会把登陆信息发给客户端,让客户端来存储,不管访问哪台服务器直接进行解密就可以,但是可能会被暴力破解的风险单点登录的redis解决方案:当登陆过来之后redis会存储登陆的信息,key存储token,value存储用户信息,然后会把token发给客户端,当下一次访问的时候携带token令牌去找redis 71. 为什么需要单点登陆系统

就是在微服务当中,每个模块都抽离开来,不可能说每个模块都要进行登陆,所以会把登陆的系统单独抽离开,客户端登陆的时候直接找单点登陆的系统,会把登陆的信息存储到redis当中,然后返回一个token给客户端,当客户端在访问其他模块的时候把token放到请求头或者请求参数里面携带token,但是不能去操作redis,不然整个模块都要去连接redis,然后它会拿着token去请求单点登陆系统,可以使用HttpClient发送请求或者是dubbo远程调用

72. 自定义starter原理

spring boot提供了强大的starter为我们简化代码编写,有时候我们可以根据需要编写自己的starter,首先会导入基本的pom依赖,因为我们知道,当spring boot启动的时候会加载meta-INF下的spring.factories配置文件中相应的配置类,所以我们会在resources目录里面创建meta-INF/spring.factories文件,然后我们再创建自动装配的类,并且为了能够在yml文件灵活的修改自动装配配置,我们还会创建一个配置类,标上注解ConfigurationProperties以及定义好前缀和相应的属性,然后再自动装配类上标上注解Configuration相当于XML,以及注解ConditionalOnClass的生效类和注解EnableConfigurationProperties来激活配置类,然后再添加相应的自动装配的配置。配置完成之后加入到spring.factories文件里,最后安装到本地仓库。

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/777896.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号