Fork me on GitHub

面经整理5

1. spring事务写在哪一部分,为什么不写在DAO,Controller层

事务写在Service层

  1. 为什么不在DAO层?

    • 在数据库中,所谓事务是指一组逻辑操作单元即一组sql语句。当这个单元中的一部分操作失败,整个事务回滚,只有全部正确才完成提交。
    • 如果放在了DAO层,每一次增删改查都将提交一次事务,那么事务的一致性就会被破坏。
    • 一般在Service的一处可以调用DAO层的多处,所以只要添加一处事务注解@Transactional,这样才能体现事务的特性。
  2. 为什么不在Controller层?

    • 不推荐,但是事实上可以实现把事务放在Controller层。
    • 一般不会将事务放在Controller层,而且直接放是会报404错误的(因为SpringMVC和Spring是两个不同的容器)。application.xml中应该负责扫描除@Controller的注解如@Service,而SpringMVC的配置文件应该只负责扫描@Controller,否则会产生重复扫描导致Spring容器中配置的事务失效。

推荐阅读:
事务为什么加在service层而不加在dao层

@transactional可以注解到controller上吗?

在Spring MVC中,事务可以加在Controller层

【严重推荐!】Transaction 在 Controller 层的探索

2. 数据库驱动为什么使用反射调用不直接new

使用Class.forName("com.mysql.jdbc.Driver") 去加载驱动的目的:

  1. 用反射检查JDBC驱动的主类com.mysql.jdbc.Driver是否存在,若不存在则表示运行环境中没有这个驱动,并进入catch段。用反射来加载的好处是,当驱动jar包不存在时,我们可以做更多的操作。
  2. 实现解耦。JDBC其实是JDK的一系列接口,对于不同数据库软件有不同的实现。使用反射后,代码中就不存在任何与实现相关的东西。如果直接用new,那么可能会引入com.mysql.*或者com.oracle.*等,与代码耦合性高。

推荐阅读:JDBC为什么使用反射加载驱动

3. 观察者模式讲一下

  1. 是一对多关系的体现。
  2. 每个观察者Observer需要被保存到被观察者的集合中,并且被观察者提供添加和删除的方式。
  3. 被观察者把自己传给观察者(使用Observer自身的构造方法),当状态改变后,通过遍历和循环的方式逐个通知列表中的观察者。
  4. 虽然解耦了观察者和被观察者的依赖,让各自的变化不大影响另一方的变化,但是这种解耦并不是很彻底,没有完全解除两者之间的耦合。

推荐阅读:观察者模式

4. 多线程之Monitor

  • Synchronized关键字包括monitor enter和monitor exit两个JVM指令。

  • Monitor是一种同步机制。使用synchronized关键字时,同一时刻只能有一个线程访问同步资源,视为“加锁”。但实际上,是该线程获取了与mutex(互斥对象,即monitor object)相关联的monitor锁。

  • 在JVM中,每一个对象都与一个monitor相关联,一个monitor的lock锁只能被一个线程在同一时间获得,在一个线程尝试获得与对象关联monitor的所有权时会发生如下的情况:

    1. 如果monitor的计数器为0,意味着该monitor的lock还没有被获得,某个线程获得之后将立即对该计数器加一,从此该线程就是这个monitor的所有者了。
    2. 如果一个已经拥有该monitor所有权的线程重入,则monitor计数器再次累加。
    3. 如果monitor已经被其他线程所拥有,那么某个线程尝试获取该monitor的所有权时,会被陷入阻塞状态知道monitor计数器变为0,才能再次尝试获取对monitor的所有权。
  • Wait()/notify()/notifyAll()方法构成监视条件(Monitor Condition)。

  • 拥有monitor锁的线程可以调用wait()进入等待队列(Wait Set),同时释放监视锁,进入等待状态。

  • 其他线程(有monitor锁的线程才有资格)调用notify()/notifyAll()方法唤醒等待队列中的线程,被唤醒的线程需要重新争取monitor锁。

推荐阅读:《Java高并发编程详解》P69 - P70
以及:Java 多线程(二)-Monitor

5. 如何使浏览器加载请求可以稳定到达后台而不使用浏览器缓存

  1. Cache-Control/Pragma这个HTTP Head字段中进行缓存禁用:

    1
    2
    Cache-Control: no-store
    Cache-Control: no-cache, no-store, must-revalidate
  2. Expires字段,后面设置过期的日期和时间。

  3. Last-Modified/EtagLast-Modified字段,一般服务端在响应头中返回一个Last-Modified字段,告诉浏览器这个页面的最后修改时间,如Last-Modified:Sat,25Feb201212:55:04GMT,浏览器再次请求时在请求头中增加一个If-Modified-Since:Sat,25Feb 201212:55:04GMT字段,询问当前缓存的页面是否是最新的,如果是最新的就返回304状态码,告诉浏览器是最新的,服务器也不会传输新的数据。

  4. Etag字段,与Last-Modified字段类似。这个字段的作用是让服务端给每个页面分配一个唯一的编号,然后通过这个编号来区分当前这个页面是否是最新的。这种方式比使用Last-Modified更加灵活,但是在后端的Web服务器有多台时比较难处理,因为每个Web服务器都要记住网站的所有资源,否则浏览器返回这个编号就没有意义了。

推荐阅读:HTTP 缓存

防止浏览器缓存的几种方法

6. eclipse、IJ开发快捷键及使用技巧讲几个

Eclipse Idea
格式化当前代码 Ctrl+Shift+F Ctrl+Alt+L
注释当前行,再按取消注释 Ctrl+/ Ctrl+/
删除当前行 Ctrl+D Ctrl+X
自动补全代码或者提示代码 Alt+/ Ctrl+空格
组织类的import导入 Ctrl+Shift+O Alt+空格

Eclipse使用技巧:

  1. 在Eclipse里文档生成是件简单的事情,只要键入“/**”,在一个声明上按enter键。
  2. 查看某个类的继承关系:选中该类,ctrl+t。
  3. 快捷键添加set、get方法,重写或实现接口的某个方法:shift+alt+s

Idea使用技巧:

  1. Ctrl+H 查看类的继承层次。
  2. Ctrl+Alt+B 可以跳转到抽象方法的实现。
  3. Ctrl-Shift-Backspace 让你调转到代码中所做改变的最后一个地方,多按几次 Ctrl-Shift-Backspace 查看更深的修改历史。
  4. 使用 Ctrl-Shift-V 快捷键可以将最近使用的剪贴板内容选择插入到文本。使用时系统会弹出一个含有剪贴内容的对话框,从中你可以选择你要粘贴的部分。
  5. Ctrl-D 可以复制选择的块或者没有所选块是的当前行。

推荐阅读:IntelliJ Idea 常用快捷键列表

eclipse使用技巧心得分享

7. classPath 与path的区别

  1. Path环境变量,作用是指定命令搜索路径
    • 比如,在命令行下 javac helloWorld.java,那么将从Path路径下找这个java文件。
    • 如何来做:把jdk安装目录下的bin目录增加到现有的PATH变量中(bin目录下有常用的javac/java/javadoc等命令)。
  2. ClassPath环境变量,作用是指定类搜索路径
    • JVM就是通过CLASSPTH来寻找类的。
    • 如何来做:需要把jdk安装目录下的lib子目录中的dt.jar和tools.jar,以及当前目录“.”一并设置到CLASSPATH中。
  3. JAVA_HOME环境变量,指向jdk的安装目录
    • Eclipse/NetBeans/Tomcat等软件就是通过搜索JAVA_HOME变量来找到并使用安装好的jdk。

推荐阅读:环境变量path和classpath的作用是什么?

8. Linux 的软链接、硬连接

先看三个概念,文件数据块、元数据、inode号,解释如下:

  1. 文件数据块(data block),也称为用户数据(user data),是记录文件真实内容的地方。
  2. 元数据(metadata),属于文件的附加属性,记录着“文件大小、创建时间、所有者”等信息。也包括了inode号。
  3. inode号,是文件的唯一标识(文件名不是唯一标识),使用inode号来查找正确的文件数据块。(流程:filename -> inode -> data blocks)。
    • 在 linux 中,可以使用命令 ls -i查看 inode。
    • 移动命令mv和重命名,并不会改变文件的“文件数据块”和 inode 号。

重点来了,硬链接与软链接,如下:

  1. 硬链接 hard link:同一个文件使用的多个别名。此时的多个 link 有着共同的 inode 及 data block,仅文件名不相同
    • 可以用命令linkln创建,后跟-d
    • 只能对已存在的文件进行创建;
    • 不能交叉文件系统创建硬链接;
    • 只能对文件创建,不能对目录进行创建;
    • 删除硬链时,不会影响其他有相同 inode 号的文件。
  2. 软链接 soft link 或者 symbolic link:若文件的文件数据块中存放的内容是另一个文件的路径名的指向时,这个文件就是软链接。软链接就是一个普通文件,只是数据块内容有点特殊,它有自己独有的 inode 号以及文件数据块。
    • 可以用命令ln -s来创建;
    • 有自己的文件属性及权限;
    • 可对不存在的文件或目录创建软链;
    • 可交叉文件系统;
    • 创建软链时,链接计数i_nlink不会增加;
      • 简单来说,i_nlink是一个inode中统计 hard link 数量的计数器
    • 删除软链不会影响被指向的文件,但如果被指向的原文件被删,则相关软链被称为死链。

软链接举例:

1
2
# ln -s 目标地址 快捷方式地址(放在 /usr/local/bin/ 目录下可以直接启动)
ln -s /usr/redis-5.0.3/src/redis-cli /usr/local/bin/redis-cli

推荐阅读:理解 Linux 的硬链接与软链接

9. Spring 中 bean 的生命周期

在 Spring 中,bean 的生命周期完全由容器控制。

最简理解(阉割版):

  1. 实例化bean;
  2. DI;
  3. 前置、后置处理;
  4. 使用;
  5. 销毁。

推荐阅读:Spring中Bean的生命周期是怎样的?

10.Mybatis批量查询手写sql

  1. 情况1,传入是一个集合时(如一个装着 id 的 List )如下:
1
2
3
4
5
6
7
8
9
10
11
// Mapper.java
public List<User> findUserListById(@Param("Ids") List<Integer> Ids);

// mapper.xml
<select id = "findUserListById" resultType="com.glory.api.jog.pojo.User">
SELECT * FROM users
WHERE id IN
<foreach collection = "Ids" item = "id" open = "(" close = ")" separator = ",">
#{id}
</foreach>
</select>
  1. 情况2,传入的是一个 Map时(Map 有一个<String,List>的键值对)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// Service
HashMap<String,Object>params = new HashMap<>();
params.put("mapList",list); // list中装着id
TestDao.findUserListByList(params);

// Mapper.java
public List<User> findUserListByList(HashMap<String,Object> params);

// mapper.xml
<select id="findUserListByList" resultType = "User">
SELECT * FROM users
WHERE id IN
<foreach item = "item" index = "index" collection="mapList"
open = "(" close = ")" separator = ",">
#{item}
</foreach>
</select>
<!--重点关注 collection 中的内容,传的是 map 中的一个 key-->
  1. 情况3,传入的是一个对象时(比如对象中有一个 list 集合及 getter、setter 方法)如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Service
public class TestModel{
private List<Integer> listDemo;
// getter setter 方法略
}
TestModel tm = new TestModel();
tm.setter(listDemo); // listDemo 中装着id
TestDao.getListByObject(tm);

// Mapper.java
public List<User> getListByObject(TestModel tm);

// mapper.xml
<select id = "getListByObject" resultType = "User">
SELECT * FROM users
WHERE id IN
<foreach item = "item" index = "index" collection = "listDemo"
open = "(" close = ")" separator = ",">
#{item}
</foreach>
</select>

<!--重点关注 collection 中的内容,传的是对象中的一个属性-->

ps :Mybatis 中的 resultType 与 resultMap的区分:

  1. resultType 是直接表示返回类型的 ,resultMap 则是对外部 ResultMap 的引用(旁边一定有个 ResultMap 的元素)。resultType 与resultMap 不能共存。

  2. 当提供的返回类型属性是resultType时,MyBatis会将Map里面的键值对取出赋给resultType所指定的对象对应的属性。所以其实MyBatis的每一个查询映射的返回类型都是ResultMap,只是当提供的返回类型属性是resultType的时候,MyBatis对自动的给把对应的值赋给resultType所指定对象的属性。

  3. 当提供的返回类型是resultMap时,因为Map不能很好表示领域模型,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。使用 resultMap 的简单查询与复杂查询:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    <!--简单查询-->
    <!--通过“BlogResult”进行映射-->
    <resultMap type="Blog" id="BlogResult">
    <id column="id" property="id"/>
    <result column="title" property="title"/> <!--如果不写这些映射,Mybatis也会自动帮封装-->
    <result column="content" property="content"/>
    <result column="owner" property="owner"/>
    </resultMap>

    <select id="selectBlog" parameterType="int" resultMap="BlogResult">
    select * from t_blog where id = #{id}
    </select>

  • 复杂查询就不写代码了,可能涉及到子节点、关联查询等,太复杂,建议直接看下面的链接。

推荐阅读:Mybatis中的resultType和resultMap

11. JVM、Tomcat与进程之间你需要知道的事情

  1. 一个 JVM 就相当于一个操作系统(实质是一个进程)。
    • JVM 的生命周期:一个 Java 程序开始执行时,JVM 才运行;程序结束时它停止执行。
  2. 一个 Java 应用程序会开启一个 JVM 进程,如果运行了三个程序,那么会有三个运行中的 JVM 进程。
  3. JVM 运行 Java 程序有两种方式:jar 包和 Class:
    1. 运行 jar 时,先拿 JNIEnv 实例,然后拿 Manifest 对象,最后拿 jar 中的文件(拿主类),然后装载该主类。
    2. 运行 Class 时,main 函数直接调用 Java.c 中的 LoadClass 方法装载该类。
  4. Tomcat 是一个 Java 程序,是一个用 Java 语言开发的免费开源的 Web 服务器。
    • Tomcat 与 Java 应用程序(webapps 目录下的 war 包)是运行在同一个 JVM 中,但是分工不同,Tomcat 相当于调度员,Java 程序相当于工人。Tomcat 处理请求的步骤如下:
      1. Tomcat 监听8080端口(假设),一个 http 请求从主机的 8080 端口发送过来,Tomcat 最先获悉;
      2. Tomcat 将该请求放入一个队列中,JVM 中有若干工作者线程会从此队列中获取任务;
      3. 假设线程 A 取得此任务,A 会分析请求的 url,并检查已加载的 Web.xml 配置,来判断要将此请求交给应用的哪个 servlet 来处理(假设应用由 servlet 来实现的);
      4. 此时应用开始干活,解析请求参数,处理业务流程,生成响应;
      5. 线程 A 把 response 回送给请求的发送端。

推荐阅读:Java JVM 运行机制及基本原理
tomcat 和 jvm 的关系

12. 通过 Class 对象获取方法集合的三种方法的区分

  1. getDeclaredMethods: 获得声明的所有方法(protected、private、default、public,但不包含继承的方法)。
  2. getMethods:获得所有 public 方法,包括继承类的 public 方法(即父类的 public 方法)。
  3. getMethod:传入方法名(以及参数表对应的多个 class,比如 int.class),获取该方法

推荐阅读:深入解析Java反射(1) - 基础

13. 如果排序 10G 的元素

问题:不能一次性放入内存中,所以需要引入外部排序。

  1. 数据源读一批数据到缓冲区;
  2. 缓冲区中读取一批数据到内存;
  3. 内存进行堆排序(或使用Priority Queue)。

Iterable merge(List<Iterable> sortedData);

-------------The End-------------