主要参考以下资料并加入一些平时学习时的问题做整理!!! 如果版权问题,立刻删除!
新版Java面试专题视频教程,java八股文面试全套真题+深度详解(含大厂高频面试真题)_哔哩哔哩_bilibili
Mybatis
1、什么是Mybatis?
- MyBatis是一款基于Java的持久层框架,它提供了一种简单的方式来映射数据库操作到Java对象。它内部封装了JDBC,开发时只需要关注SQL语句本身,不需要花费精力去处理加载驱动、创建连接、创建statement等繁杂的过程。程序员直接编写原生态SQL,可以严格控制SQL执行性能,灵活度高。
- MyBatis可以使用XML或注解来配置映射原生信息,将POJO映射成数据库中的记录,避免了几乎所有JDBC代码和手动设置参数以获取结果集
- 通过XML文件或注解的方式将要执行的各种statement配置起来,并通过Java对象和statement中的SQL的动态参数进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射为Java对象并返回
2、说说MyBatis的优点和缺点?
- 优点
- 基于SQL语句编程,相当灵活,不会对应用程序或数据库的现有设计造成任何影响,SQL写在XML里,解除SQL与程序代码的耦合(用注解写就另说了),便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
- 与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接
- 提供应设标签,支持对象与数据库的ORM字段关系映射,提供对象关系映射标签,支持对象关系组件维护
- 缺点
- SQL语句编写的工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库
- XML配置较为繁琐,MyBatis的配置文件中需要编写大量的XML代码来描述SQL语句和映射关系,这可能会使配置文件显得较为繁琐
3、#{}
和${}
的区别是什么?
#{}
在SQL语句中表示一个占位符,它可以防止SQL注入攻击,并且可以自动进行参数类型转换。在执行SQL语句时,MyBatis会将参数值以安全的方式设置到SQL语句中。底层使用的是PreparedStatement
,#{}
占位符替换为?
。${}
在SQL语句中也表示一个占位符,但它不会对参数进行任何处理,直接将参数值拼接到SQL语句中,因此容易引发SQL注入攻击。底层使用的是Statement
4、MyBatis执行流程是什么样子的?
- ①读取MyBatis配置文件:mybatis-config.xml加载运行环境和映射文件
- ②构造会话工厂SqlSessionFactory,一个项目只需要一个,单例的,一般由 spring进行管理
- ③会话工厂创建SqlSession对象,这里面就含了执行SQL语句的所有方法
- ④操作数据库的接口,Executor执行器,同时负责查询缓存的维护
- ⑤Executor接口的执行方法中有一个MappedStatement类型的参数,封装了 映射信息
- ⑥输入参数映射
- ⑦输出结果映射
5、Mybatis是否支持延迟加载?延迟加载的底层原理知道吗?
-
支持,在Mybatis配置文件中,可以配置是否启用延迟加载
lazyLoadingEnabled=true|false
,默认是关闭的或者在mapper文件中设置fetch_type=lazy
-
延迟加载在底层主要使用的CGLIB动态代理完成的
- 使用CGLIB创建目标对象的代理对象,这里的目标对象就是开启了 延迟加载的mapper
- 当调用目标方法时(获取另一个实体对象),进入拦截器invoke方法,发现目标方法是null 值,再执行sql查询(懒加载)
- 获取数据以后,调用set方法设置属性值,再继续查询目标方法,就 有值了
6、Mybatis的一级、二级缓存用过吗?
-
mybatis的一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,SqlSession级别,**默认开启。**同一个SqlSession内部的多次查询操作,如果查询的参数和查询语句都相同,那么第一次查询时查询结果会被缓存到一级缓存中,后续的查询操作会直接从缓存中获取结果,而不会再去查询数据库。一级缓存的生命周期与SqlSession的生命周期相同,当SqlSession被关闭时,一级缓存也会被清空。
-
二级缓存是基于namespace和mapper的作用域起作用的,不是依赖于SQL session,默认也是采用 PerpetualCache,HashMap 存储。**因此当多个SqlSession操作同一个Mapper时,他们之间共享一个二级缓存。二级缓存需要单独开启,二级缓存的缓存时间是无限制的,但是它的缓存策略是基于LRU(最近最少使用)算法的。**为了提高缓存命中率,MyBatis还提供了缓存清空机制,可以通过在Mapper中配置
flushCache="true"
来清空缓存。此外,MyBatis还支持基于注解的缓存配置,可以通过在Mapper接口或方法上添加@CacheNamespace
或者@CacheNamespaceRef
注解来配置缓存-
1,全局配置文件
<settings> <setting name="cacheEnabled" value="true> </settings>
-
2,映射文件
使用
<cache/>
标签让当前mapper生效二级缓存
-
-
二级缓存的注意事项
- 1,对于缓存数据更新机制,当某一个作用域(一级缓存 Session/二级缓存Namespaces)的进行了新增、修改、删除操作后,默认该作用域下所有 select 中的缓存将被 clear。
- 2,二级缓存需要缓存的数据实现Serializable接口。
- 3,只有会话提交或者关闭以后,一级缓存中的数据才会转移到二级缓存中。
7、插入数据后如何获取插入数据库数据的主键?
- 在Mapper 接口中的方法上添加一个
@Options
注解,并在注解中指定属性useGeneratedKeys=true
和keyProperty="实体类属性名"
- Mybatis Plus: 自动将主键值回写到实体类中
8、如何处理Java实体类属性名和数据库表中的字段名不一致的情况?
-
如果实体类属性名和数据库表查询返回的字段名不一致,Mybatis不能自动封装,将会导致这些字段的值为null。
-
解决方法如下:
- 在SQL语句中,对不一样的列名起别名,别名和实体类属性名一样
@Select("select id, username, password, name, gender, image, job, entrydate, " + "dept_id AS deptId, create_time AS createTime, update_time AS updateTime " + "from emp " + "where id=#{id}") public Emp getById(Integer id);
- 手动结果映射:通过 @Results及@Result 进行手动结果映射
@Results({@Result(column = "dept_id", property = "deptId"), @Result(column = "create_time", property = "createTime"), @Result(column = "update_time", property = "updateTime")}) @Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}") public Emp getById(Integer id);
- 开启驼峰命名(推荐):如果字段名与属性名符合驼峰命名规则,mybatis会自动通过驼峰命名规则映射。但是要使用驼峰命名前提是实体类的属性与数据库表中的字段名严格遵守驼峰命名。
# 在application.properties中添加: mybatis.configuration.map-underscore-to-camel-case=true
9、MyBatis是如何进行分页的?分页插件的原理是什么?
- MyBatis使用RowBounds对象进行分页,它是针对ResultSet结果及执行的内存分页,而非物理分页。可以在SQL内直接拼写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页,例如在原有SQL后面拼写limit
- 分页插件的基本原理是使用MyBatis提供的插件接口,实现自定义插件,在插件的拦截方法内拦截待执行的SQL,然后重写SQL,根据dialect方言,添加对应的物理分页语句和物理分页参数
10、Mybatis使用注解方式开发还是使用XML方式开发?
- 使用Mybatis的注解,主要是来完成一些简单的增删改查功能。如果需要实现复杂的SQL功能, 建议使用XML来配置映射语句。
11、什么是动态SQL?怎么用?
- 动态SQL:SQL语句会随着用户的输入或外部条件的变化而变化, 比如只按照
name
或者gender
查询, 不一定每个字段都需要赋值。 <if>
用于判断条件是否成立,如果条件为true,则拼接SQL 形式。where
元素只会在子元素有内容的情况下才插入where子句,而且会自动去除子句的开头的 AND或OR。- 动态地在行首插入
SET
关键字,并会删掉额外的逗号。(用在update语句中)
12、XML映射文件中,除了常见的select、insert、update、delete标签外,还有哪些标签?
<resultMap>
:用于定义查询结果与Java对象的映射关系,可以自定义属性的映射关系,包括一对一、一对多等复杂映射关系<sql>
:用于定义可重用的SQL片段,可以将SQL片段抽象出来,供多个SQL语句使用<include>
:用于包含其他XML文件中定义的SQL片段,可以实现SQL的复用<if>
:用于在SQL语句中添加条件判断,可以根据参数动态生成SQL语句<where>
:用于将多个条件组合成一个WHERE子句,避免生成无用的WHERE子句<foreach>
:用于遍历集合或数组,并将元素拼接成SQL语句中的IN子句<choose>
、<when>
、<otherwise>
:用于实现复杂的条件判断,类似Java中的if-else语句<trim>
:用于对SQL语句进行字符串处理,如去除逗号、括号等
13、MyBatis实现一对一有几种方式?具体怎么操作的?
-
使用
<resultMap>
标签定义一对一映射关系- 例如,我们有一个Order类和一个User类,一个订单只属于一个用户,因此Order类中包含一个User对象作为属性。在XML映射问建中,我们可以定义一个
<resultMap>
标签,定义Order类与User类之间的映射关系
<resultMap id="orderMap" type="Order"> <id property="id" column="id"/> <result property="name" column="name"/> <result property="userId" column="user_id"/> <association property="user" javaType="User" select="getUserById"/> </resultMap>
- 在上面的标签中,我们使用标签定义Order类中的user属性与User类之间的映射关系。其中,property属性指定Order类中的user属性,javaType属性指定User类的类型,select属性指定通过getUserById查询用户信息的SQL语句。
- 例如,我们有一个Order类和一个User类,一个订单只属于一个用户,因此Order类中包含一个User对象作为属性。在XML映射问建中,我们可以定义一个
-
使用嵌套查询
- 使用嵌套查询可以在查询订单的同时查询用户信息,将查询结果组装成一个Order对象,例如我们在XML映射文件中定义以下SQL语句
<select id="getOrderById" resultMap="orderMap"> select o.id, o.name, o.user_id, u.username, u.phone, u.address from orders o join users u on o.user_id = u.id where o.id = #{id} </select>
- 在上面的SQL语句中,我们通过join语句关联了
orders
表和users
表,并查询了订单和用户的信息。在<select>
标签中,我们通过resultMap
属性指定了将查询结果映射到Order对象上的<resultMap>
标签。在<resultMap>
标签中,我们通过<association>
标签定义了Order
对象和User
对象之间的映射关系。
14、MyBatis中见过什么设计模式?
- 工厂模式:MyBatis中的SqlSessionFactory和SqlSession都是通过工厂模式创建的,SqlSessionFactoryBuilder类的作用就是创建SqlSessionFactory对象。
- 代理模式:MyBatis中的Mapper接口实现类是通过JDK动态代理实现的。在执行Mapper接口中的方法时,实际上是通过动态代理生成一个代理对象,代理对象调用方法是会自动执行相应的SQL语句
- 装饰器模式:MyBatis中的插件就是通过装饰器模式实现的,插件可以在不修改原有代码的基础上,增强或修改原有功能
- 享元模式:MyBatis中的缓存就是使用了享元模式,通过缓存已经查询过的数据,避免重复查询数据库
- 观察者模式:MyBatis中的拦截器就是使用了观察者模式,拦截器可以在SQL执行前后进行一些额外操作
15、MyBatis中例如UserMapper.java是接口,为什么没有实现类还能调用?
- 在MyBatis中,Mapper接口是由MyBatis框架动态生成的。当我们定义一个Mapper接口时,MyBatis会根据接口的定义和XML映射文件中的配置信息,动态生成一个接口的实现类,并将其注册到Spring容器中,供应用程序使用。
- MyBatis使用了Java动态代理技术来生成Mapper接口的实现类。当应用程序调用Mapper接口中的方法时,实际上是调用了Mapper接口的代理对象的方法。代理对象会根据方法名和参数类型等信息,动态生成相应的SQL语句,并将其执行。由于动态代理是在运行时生成的,因此不需要Mapper接口的实现类
- 这种通过接口定义方法,而不需要手动实现的方式,成为面向接口编程。它可以降低代码的耦合度,使得应用程序更加灵活和可扩展
16、JDBC编程有哪些步骤
-
加载数据库驱动程序。使用
Class.forName()
方法加载对应的数据库驱动程序,如Class.forName("com.mysql.jdbc.Driver")
加载MySQL数据库驱动程序 -
创建数据库连接,使用
DriverManager.getConnection()
方法创建数据库连接,需要指定数据库连接字符串、用户名、密码等连接信息,例如Connection conn = DirverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "username", "password");
-
创建Statement或者PreparedStatement对象,用于执行SQL语句。
Connection.createStatement(); Connection.prepareStatement();
-
执行SQL语句,使用Statement或PreparedStatement对象的
execute()
、executeQuery()
、executeUpdate()
等方法执行SQL语句。 -
处理查询结果,如果执行的是查询语句,需要使用ResultSet对象获取查询结果
-
关闭ResultSet、Statement、Connection独享。释放占用的资源,防止出现内存泄漏
MybatisPlus
1、MP没写在哪个表里查,为什么能自动识别是在我刚刚创建的表里查?
- 默认情况是在同名的表中查找
- 继续探究:那我要是表明和模型类的名不一样,那咋整?
- 在模型类的上方加上
@TableName
注解
- 在模型类的上方加上
2、@TableField注解有什么用?
-
表字段与模型类编码属性不一致时,实现映射关系
-
编码中添加了数据库中未定义的属性,通过exist属性,设置不存在在数据库表中
-
查询表中所有的列的数据,就可能把一些敏感数据查询到返回给前端,这个时候我们就需要限制哪些字段默认不要进行查询。解决方案是
@TableField
注解的一个属性叫select
,该属性设置默认是否需要查询该字段的值,true(默认值)表示默认查询该字段,false表示默认不查询该字段。@TableName("tb_user") @Data public class User { private Long id; private String name; @TableField(value = "pwd",select = false) private String password; private Integer age; private String tel; @TableField(exist = false) private Integer online; }
3、@TableId注解用于表示主键的属性定义上方,通过type属性生成主键ID的增长策略,有哪些?
- NONE: 不设置id生成策略
- INPUT:用户手工输入id
- AUTO:数据库ID自增,这种策略适合在数据库服务器只有一台的情况下使用,不可作为分布式ID使用
- ASSIGN_ID:雪花算法生成id(可兼容数值型与字符串型),可以在分布式的情况下使用,生成的是Long类型的数字,可以排序,性能也高,但是生成的策略与服务器时间有关,如果修改了系统时间,也有可能出现重复的主键。
- ASSIGN_UUID:以UUID生成算法作为id生成策略。可以在分布式的情况下使用,而且能够保证ID唯一,但是声称的主键是32位的字符串,长度过长占用空间,而且不能排序,查询性能也慢
注意:其他的几个策略均已过时,都将被ASSIGN_ID和ASSIGN_UUID代替掉。
- 如果在项目中的每一个模型类上都需要使用相同的生成策略,可以在YML文件中配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id
4、分布式ID是什么?
- 当数据量足够大的时候,一台数据库服务器存储不下,这个时候就需要多台数据库服务器进行存储
- 比如订单表就有可能被存储在不同的服务器上
- 如果用数据库表的自增主键,因为在两台服务器上所以会出现冲突
- 这个时候就需要一个全局唯一ID,这个ID就是分布式ID
5、MP如何实现逻辑删除?
- 修改数据库表,添加
deleted
列 - 实体类添加属性,标识新增的字段为逻辑删除字段,使用
@TableLogic
//表名前缀和id生成策略在yml配置文件写了
@Data
public class User {
private Long id;
private String name;
private String password;
private Integer age;
private String tel;
//新增delete属性
//value为正常数据的值(在职),delval为删除数据的值(离职)
@TableLogic(value = "0",delval = "1")
private Integer deleted;
}
如此操作,查询时会默认添加查询语句deleted=0
继续探究:如果还是想把已经删除的数据都查询出来该如何实现呢?
@Mapper
public interface UserDao extends BaseMapper<User> {
//查询所有数据包含已经被删除的数据
@Select("select * from tb_user")
public List<User> selectAll();
}
继续探究:如果每个表都要有逻辑删除,那么就需要在每个模型类的属性上添加@TableLogic
注解,如何优化?
- 在配置文件中添加全局配置,如下
mybatis-plus:
global-config:
db-config:
# 逻辑删除字段名
logic-delete-field: deleted
# 逻辑删除字面值:未删除为0
logic-not-delete-value: 0
# 逻辑删除字面值:删除为1
logic-delete-value: 1
6、MP如何是实现乐观锁?
乐观锁:主要用于解决小规模的并发问题,因为数据库本身的性能就是个瓶颈,如果对其并发超过2000以上的就需要考虑其他解决方案了
实现步骤:
-
数据库表添加一列version,默认值设为1
-
在模型类中添加对应的属性
@Data public class User { private Long id; private String name; private String password; private Integer age; private String tel; @TableLogic(value = "0", delval = "1") private Integer deleted; @Version private Integer version; }
-
添加乐观锁拦截器
@Configuration public class MpConfig { @Bean public MybatisPlusInterceptor mpInterceptor() { //1.定义Mp拦截器 MybatisPlusInterceptor mpInterceptor = new MybatisPlusInterceptor(); //2.添加乐观锁拦截器 mpInterceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return mpInterceptor; } }