Java 面试复盘 (2021-03-31)

java 中关于自定义注解了解多少?自定义注解如何定义?

注解是 Java 提供的设置程序中元素的关联信息和元数据的方法,是一个借口。程序可以通过反射来获取指定程序中元素的注解对象,然后通过该注解对象获取注解中的元数据信息。

在 Java 中,定义了 4 个标准的元注解,用来定义不同类型的注解:

  1. @Target:说明注解所修饰的对象范围,明确其修饰的目标。取值类型有:

     TYPE: 用于描述类、接口(包括其他注解)、枚举;
     FIELD: 用于描述域;
     METHOD: 用于描述方法;
     PARAMETER: 用于描述参数;
     CONSTRUCTOR: 用于描述构造器
     LOCAL_VARIABLE: 用于描述局部变量
     PACKAGE: 用于描述包
     ANNOTATION_TYPE: 用于声明注解
     TYPE_PARAMETER: 用于声明普通变量
     TYPE_USE: 可以标注任何类型
    
  2. @Retention:定义该注解的保留级别,即被描述的注解在什么级别下有效。有 3 个值:

     SOURCE: 在源文件中有效,即在源文件中被保留;
     CLASS:在 Class 文件中有效,即在 Class 文件中被保留;
     RUNTIME: 在运行时有效,即在运行时被保留;
    
  3. @Document: 表明该注解可以被 javadoc 工具记录,可以被 javadoc 类的工具文档化;

  4. Inherited: 表明某个被标注的类是被继承的。即如果使用 @Inherited 修饰的注解被用于一个类,那这个注解也被用于这个类的子类。

mybatis 的 # 和 $ 的区别

#{} 会将传入的 值按照字符串形式处理,首先对其进行预编译,将 #{} 替换为占位符,然后将值作为参数传递,执行时在将占位符替换为参数的值,可以有效的防止 SQL 注入。

${} 是直接替换字符串,就是简单的字符串拼接,不做任何额外的处理,存在 SQL 注入的风险,不推荐使用,但是在动态传递表名、列名等时,由于如果使用 #{} 会导致表名、列名两端自动加上单引号而报错,所以不得不使用 ${} 来原样传递并拼接 SQL。

什么是预编译?mybatis 对 # 的预编译中都进行了什么?

预编译功能可以有效的避免 SQL 注入,因为 SQL 已经编译完成,其结构已经固定,用户能做的只是传参数,无法再破坏 SQL 的结构。

预编译功能还可以提高 SQL 的执行效率。用户发送 SQL 到服务器时,服务器首先会检验 SQL 语法是否正确,然后将其编译成可执行的函数,最后将参数传递给编译后的函数,执行 SQL。而校验、编译、传参都会花费额外的时间,甚至比单独执行 SQL 的时间还要多。但是,预编译语句的优势在于:一次编译,多次运行。当多次执行某条 SQL 语句时,只会对 SQL 语句进行一次校验和编译,所以整体效率要高很多。

预编译主要经历三个步骤:

  • 执行预编译语句。将 SQL 进行预编译,此时 #{xxx} 变为占位符 ?
  • 设置参数。设置参数的映射关系。例如 xxx = 'amborse'
  • 执行 SQL 语句。调用执行方法,执行编译后的 SQL,并将参数传递给它。

此后,如果再执行这条 SQL,则不需要第一步,只需要设置参数、执行 SQL 两步。

mybatis 和 hibernate 各自的优势体现在哪些方面?

MyBatis 学习成本较低,使用简单,手动编写 SQL 语句,不需要学习其他额外的知识,并且,SQL 更加容易按照业务需求定制化,方便更加细致的优化,提高效率。

Hibernate 学习成本较高,需要学习大量的标签、配置等,但其自动化 DAO 开发比 Mybatis 简单很多,并且可移植性强,另外,Hibernate 对查询对象有良好的管理机制,二级缓存出现脏数据时会自动报错提示,而且还可以使用第三方缓存,而 Mybatis 在二级缓存中要自行考虑是否会产生脏数据的情况。

hibernate 也可以用 HQL 写定制化 SQL,那就在 sql 的灵活性这一方面,mybatis 还有优势吗?

Hibernate 通过 HQL 确实可以定制化 SQL,但是 HQL 却很少被使用。首先,Hibernate 框架的特点就是简洁化、无 SQL 编程,使用 HQL 从一定的角度上破坏了 Hibernate 原本的简洁性原则,另外,HQL 虽然与 SQL 极度相似,但也有不同之处,也加大了学习的成本。

git 的 merge 和 rebase 的区别

参考官方文档:3.2 Git 分支-分支的新建与合并3.6 Git 分支 - 变基

数据库的隔离级别,不同的隔离级别下会造成什么现象?

标准的 SQL 规范中规定了 4 种事务隔离级别,分别如下:

  • Read Uncommitted (读未提交)

    • 一个事务在执行过程中,可以访问其他事务未提交的数据(包括插入/修改)。
    • 一个事务如果已经开始写数据,则另外的事务不允许同时写操作,但允许其他事务读此行数据。
    • 此隔离级别可防止丢失更新
    • 此隔离级别会导致脏读。脏读指一个事务读取到另一个事务未提交的数据。
  • Read Committed (读已提交)

    • 一个事务在执行过程中,可以访问其他事务成功提交的数据(包括插入/修改)。
    • 读取数据的事务允许其他事务继续访问该行数据。
    • 未提交的写事务会禁止其他事务访问该行数据。
    • 此隔离级别可防止脏读。
    • 此隔离级别会导致不可重复读。不可重复读指一个事务对同一行数据读取两次,得到的结果不同。
  • Repeatable Read (可重复读)

    • 一个事务在执行过程中,可以访问其他事务成功提交的插入数据,但不可以访问成功提交的修改数据。
    • 读取数据的事务会禁止写事务,但允许读事务。
    • 写入数据的事务会禁止其他任何事务。
    • 此隔离级别可防止脏读和不可重复读。
    • 此隔离级别会导致幻读。幻读指一个事务执行两次查询,第二次查询的结果包含了第一次查询中未出现的数据。
  • Serializable (可串行化)

    • 严格的事务隔离。
    • 要求事务序列化执行,不允许并发执行。
    • 此隔离级别可有效防止脏读、不可重复读、幻读。
    • 此隔离级别会导致大量超时和锁竞争,性能极低。

总而言之,事务的隔离级别越高,越能保证数据的完整性和一致性,但同时对并发性能的影响也越大。通常,数据库的隔离级别设置为 Read Committed,防止最常见的脏读,同时也有良好的性能,对于其可能导致的不可重复读和幻读,可以动过应用程序在代码中使用悲观锁或者乐观锁来控制。

mysql 和 oracle 的区别

  • Oracle 属于大型数据库,收费且价格昂贵,且对硬件要求较高,消耗内存较大;MySQL 属于中小型数据库,开源免费,占用内存小;
  • Oracle 对高并发、大访问量场景支持较好;
  • Oracle 支持事务,MySQL 默认不支持事务,需要借助 innodb 引擎才可以支持;
  • Oracle 使用行级锁,对资源锁定的粒度较小,加锁在数据行上,不依赖索引;MySQL 以表级锁为主,对资源的锁定粒度大。如果一个 Session 对某个表加锁时间过长,会导致该表一直无法更新,这也是说 Oracle 对并发支持更好的原因;
  • Oracle 默认不提交数据,且是将提交的 sql 写入联机日志文件,保存在磁盘上,如果出现宕机,重启后可以联机日志恢复提交的数据;MySQL 默认提交数据,如果更新过程中宕机,可能会导致数据丢失;
  • Oracle 默认的隔离级别是 read committed,并且 Oracle 可以构造多版本数据块实现读一致性;MySQL 默认 repeatable read 隔离级别;
  • Oracle 逻辑备份时不锁定数据,且备份数据是一致的;MySQL 逻辑备份时会锁定数据才能保证数据一致性,影响 DML 操作
  • Oracle 的 SQL 语法较传统,不提供分页、自增等方法,需要手动实现;MySQL 对 SQL 语句有很多扩展,比如主键自增、limit 分页等;

Oracle 中 <、>、<>、!=,对于该列的 null 值是如何判定的? null 和 空字符串有什么区别?

在 oracle 数据库中,null 值只能用 is null 或者 is not null 判断,因为 oracle 中,null 值是一个特殊存在,它不等于任何值,包括 null 值本身。

比如:select count(*) from (select null col from dual) where col = null; 这条 sql 返回的结果为 0。

另外,oracle 中,null 值与任何值得运算结果都为 null

比如:select count(*) from (select null + 1 col from dual) where col is null; 返回的结果是 1。但是聚合函数的计算一般会自动剔除 null 值。

而空字符串分两种情况,一种是无长度的空字符串:'',这种情况也只能使用 is null 或者 is not null 判断,不能使用 where col = '' 来判断;
另一种是有长度的空字符串:' '(有一个空格),这种情况可以使用 where col = ' ' 来判断,使用 is null 或者 is not null 作为条件反而查不到;

Oracle 中 order by 时对于 null 值是如何处理的?

在 Oracle 中,默认情况下, null 值是最大的。但是可以通过 nulls first 或者 nulls last 来控制 null 值排序在最前面还是最后面,并且是可以和 descasc 搭配使用的。

Oracle sql 语句的优化从哪些方面入手?

首先,从 Oracle 解析器的工作机制入手,尽量减少 SQL 语句在解析器中的耗时:

  • SQL 尽量使用大写。Oracle 中 SQL 语句是不区分大小写的,是因为 Oracle 的解析器会将所有的 SQL 语句都转换为大写,然后再执行;

  • 合理排序表名,通常效率高的放在右边。Oracle 的解析器按照从右向左的顺序处理 FROM 关键字后的表名,因此,最右侧的表最先被扫描,然后作为驱动表,与前一个表关联,如果有三个及以上表,则交叉表将作为驱动表;

  • 合理排序查询条件,通常可过滤大量数据的写在右端。Oracle 的解析器按照从右向左的顺序处理 WHERE 子句,最末端的条件将最先被执行;

  • 减少对表的查询操作,能查一次的决不查两次。如:
    SELECT NAME FROM TABLE_A WHERE NAME = (SELECT NAME FROM TABLE_B WHERE ID = 1) AND AGE =(SELECT AGE FROM TABLE_B WHERE ID = 1) 优化为:
    SELECT NAME FROM TABLE_A WHERE (NAME, AGE) = (SELECT NAME, AGE FROM TABLE_B WHERE ID = 1)

  • 多使用别名,尤其多张表操作时。使用别名.列名不但可以减少解析器解析的时间,还可以避免因为列名歧义导致的错误;

  • 合理创建索引,并正确的使用索引。

    索引并非一定能提高效率,且创建索引也需要付出内存空间的代价,创建索引,一般遵循以下几个原则:

    以下,仅为创建索引的基本原则,并非固定不变的,具体情况视需求而定,建议多使用执行计划查看索引的命中情况及优化效果再做具体变更。

    • 主键、外键,建立索引
    • 查询条件,建立索引
    • 关联条件,建立索引
    • 特殊数据类型,不建立索引
    • 查询很少的列,不建立索引
    • 列的值维度太大或太小的,不建立索引
    • 需要计算或处理的列,不建立索引
    • 频繁 DML 操作的表,建立索引
    • 命中率低,必要性不大的索引,及时删除索引
  • 尽量避免全表扫描,导致索引失效。

    在 Oracle 中,有一些特定情况和一部分关键字会导致索引失效。可以使用其他的写法或者关键字替换之,达到优化的效果,但最终优化是否成功,结果还需要参考执行计划,以下仅供参考。

    • 避免在索引列进行计算。

      如:
      SELECT NAME FROM USER WHERE SALARY * 12 > 100000;
      替换为:
      SELECT NAME FROM USER WHERE SALARY > 100000 / 12;(实际场景中应尽量较少除法的使用,但这是精度问题,与效率关系不大)

    • 避免在索引列使用 NOT、<>、!=、OR。

      如:
      SELECT NAME FROM USER WHERE NOT AGE = 18;
      或:
      SELECT NAME FROM USER WHERE AGE <> 18;
      或:
      SELECT NAME FROM USER WHERE AGE < 18 OR AGE > 18;
      使用 UNION ALL 替换:

      SELECT NAME FROM USER WHERE AGE < 18
      UNION ALL
      SELECT NAME FROM USER WHERE AGE > 18;
      
    • 避免在索引列使用类型转换。如 TO_CHAR、TO_DATE、TO_NUMBER 等

  • 嵌套 SELECT 尽量将筛选条件内置。减少每次查询时源数据的数量。

  • 多使用 explainsql trace 等分析 sql 执行的顺序、性能、次数、时间等,依据实际情况进行调整优化

redis 在一个实际项目中的使用场景?当时为什么决定使用 redis,使用后有什么效果?

redis 中存储一亿条数据大概占用多少内存空间?线上环境中如果遇到 redis 内存不足该如何处理?

redis 中正常存储一亿条普通数据大约占用 10G 内存空间。(PS:据网络资料,redis 中使用位图存储 10 亿数据仅需 100M,未做验证)

如果线上环境突遇内存不足异常,我个人认为,首先要做的,不是排查原因,而是想办法保证线上环境的继续运行,为排查争取时间。

所以,应该先申请追加内存,或者在保证不会影响系统后期使用的前提下对数据进行整理、备份,然后在合适的时机对 redis 进行重启,保证系统线上环境继续运行。然后抓紧时间进行问题原因排查,一般导致内存不足的原因有两个大的方面:一是因为运行异常导致内存不足,最显著的特点就是内存占用率突然大幅上升;二是因为正常运行积累的数据量太大导致的内存不足,这种情况下可以从优化存储结构、优化内存碎片、清洗无意义数据等各方面着手进行优化(可参考Redis 内存不足优化)。

最后补充一点,Redis 的内存使用理论上来说都是会有预留空间和预警空间的,所以一般内存在达到预警值时,就需要着手处理了,而不是等到线上遇到内存不足的异常。所以,如果没有设置预警值或者说预警功能失效了,在解决完内存异常之后需要再次处理测试一下。

对物化视图的理解。物化视图和普通视图有什么不同?

主要参考以下两篇文章整理:Oracle 物化视图普通视图和物化视图的区别

物化视图(Materialized View) 和视图类似,其目的都是反应某个查询的结果,但物化视图存储的是基于远程表的数据,虽然称之为视图,但其更像一种物理表,只不过这种物理表需要通过 Oracle 内部机制来定期更新。

物化视图的更新有 4 种:

  • FAST

    快速刷新。采用增量刷新,只刷新自上次刷新后修改过的部分。

  • COMPLETE

    完全刷新。刷新整个物化视图。

  • FORCE

    强制刷新。会优先快速刷新,如果无法使用快速刷新,则使用完全刷新。

  • NEVER

    从不刷新。

物化视图的类型有 2 种:

二者的区别在于刷新方式不同:

  • ON DEMAND

    仅在物化视图需要被刷新时,才进行刷新,以保证数据和基表数据的一致性。可以指定刷新频率,如下:

    create materialized view mv_name 
    	refresh force -- 更新方式
    	on demand -- 类型
    	start with sysdate 
    	next sysdate + 1; -- 每天刷新一次(也可以指定具体时间)
    
  • ON COMMIT

    一旦基表有 COMMIT,则立即刷新物化视图,使数据和基表一致。

生成数据的方式有 2 种:

  • BUILD IMMEDIATE

    默认。在创建物化视图时就生成数据

  • BUILD DEFERRED

    在创建物化视图时不生成数据,以后根据需要生成数据


物化视图与普通视图最大区别在于

普通视图并不存储任何数据,它就是相当于定义一个 SQL,使在编写其他 SQL 时,可以直接使用这个名称,简化设计、清晰编码,而实质上,它在解析时,仍然是将普通视图转换为对应的定义 SQL 去查询获取数据,并不会对查询性能有实质上的提升。

物化视图是将数据转换成一张物理表,是实际存储着数据的,对于大量数据操作时,性能提升相当明显。

平时喜欢从哪些途径学习?

关于 spring cloud 微服务环境的搭建过程

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

HolaSecurity

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值