典型回答:
ORM(Object-Relational Mapping,对象关系映射)框架是一种将关系型数据库中的数据 与 应用程序中的对象进行映射的技术。它通过在程序代码中定义的类和属性来表示数据库表和字段,从而让开发人员能够以面向对象的方式来操作数据库。
ORM 框架的主要目的是减少应用程序与数据库之间的耦合度,提高开发效率,同时保持数据的一致性和安全性。常见的 ORM 框架有 MyBatis、Hibernate、Spring Data JPA 等。
MyBatis 有哪些优缺点?
MyBatis 作为一款轻量级的 ORM 框架,它的优缺点如下:
优点分析:
缺点分析:
所以,MyBatis 适合那些希望性能和灵活上有更多掌控权,并且愿意付出额为 SQL 编写成本的项目。而强调开发效率,可以使用 Hibernate 或 Spring Data JPA。
典型回答:
MyBatis、Hibernate、Spring Data JPA 都是 ORM 数据库的持久化框架,但它们都有特点,并适用于不同的应用场景,下面分别说明它们的主要区别:
所以,总结来说:
典型回答:
在 MyBatis 中,如何程序中的属性名和字段名不一致,会导致查询和其他操作为 NULL 的情况,而它常见解决方案有以下几个:
public class UserInfo {
@TableField("username")
private String loginname;
}
映射之后,查询或其他操作就不会出现 NULL 问题。
典型回答:
MyBatis 执行流程如下:
总结一下:先加载配置文件,然后构建 SqlSessionFactory 对象,通过 SqlSessionFactory 类构建 SqlSession 对象,然后利用 SqlSession 对象获取 Mapper 接口的实例,进行 SQL 语句执行,根据映射关系配置,返回对应结果给对象上,最后关闭 SqlSession。
典型回答:
${} 和 #{} 都是 MyBatis 中用来替换参数的特殊标识,其用法如下:
@Delete("delete from remark where rid = #{rid}")
int del(@Param("rid") Integer rid);
但它们二者区别很大,它们的主要区别如下:
也就是说,为了防止安全问题,所以大部分场景都要使用 #{} 替换参数,但是如果传递的是 SQL 关键字,例如 order by xxx **asc/desc **时(传递 asc 或 desc),一定要使用 ${},因为它需要在执行时就被替换成关键字,而不能使用占位符替代(占位符不能用于 SQL 关键字,否则会报错)。
在传递 SQL 关键字时,一定要用 ${},但使用之前,一定要过滤和安全检查,以防止 SQL 注入。
典型回答:
SQL 注入即是指应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在应用程序中实现定义好的查询语句的末尾添加额为的 SQL 语句,在管理员不知道的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
也就是所谓的 SQL 注入指的是,使用某个特殊的 SQL 语句,利用 SQL 的执行特性,绕过 SQL 的安全检查,查询到本不该查询的结果。
比如以下代码:
<select id = "doLogin" resultType="com.example.demo.model.User">
select * from userinfo where username = '${name}' and password = '${pwd}'
select>
sql 注入代码: 'or 1 = '1
从上述结果中可以看出,以上程序在应用程序不知情的情况下实现了非法操作,以此来实现欺骗数据库服务器执行非授权的铭感数据。
如何防止 SQL 注入?
防止 SQL 注入常见方法有以下两种:
String sql = "select * from users where username = ? and password = ?";
PreparedStatement pstmt = connection.prepareStatement(sql);
pstmt.setString(1,UserInputUsername);
pstmt.setString(2,UserInputUsername);
ResultSet rs = pstmt.executeQuery();
典型回答:
MyBatis 中分页有以下两种方式:
① 物理分页:
<select id = "getUserList" resultType = "User">
select * from user
limit #{limit} offset #{offset}
select>
PageHelper.startPage(1,10);
List<User> list = userMapper.selectIf(1);
PageHelper 实现原理解析
PageHelper 底层使用了 MyBatis 的拦截器(Interceptor)机制,在 MyBatis 进行查询时,拦截并对 SQL 语句进行动态修改(添加 limit 等分页查询操作),之后查询数据库、并对查询结果进行封装,包装成分页对象(如包含数据列表、总记录数、总页数等信息的分页对象),最后将这个分页对象返回给客户端。
② 逻辑分页:
MyBatis 自带的 RowBounds 进行分页就是逻辑分页,它是一次性查询很多语句,然后在数据中再进行检索,实现代码如下:
RowBounds rowBounds = new RowBounds(offset,limit);
List<User> users = sqlSession.selectList("getUserList",null,rowBounds);
其中 offset 为起始行偏移量,limit 是每页数据量,虽然只设置两个值,但是使用 RowBounds 时,它会一次性查询多条数据,然后在内存中进行 offset 和 limit 筛选,最后在返回符合结果的数据。
典型回答:
MyBatis Plus 中实现分页功能,只需要以下两步
具体操作如下:
① 配置分页拦截器
@Configuration
public class PageConfig{
@Bean
public MyBatisPlusInterceptor mybatisPlusInterceptor(){
MyBatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 将 MP 里面的分页插件设置 MP
interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
return interceptor;
}
}
因为 MP 内置了 PaginationInnerInterceptor 插件,所以可以在拦截器此处直接添加 new PaginationInnerInterceptor() 的代码。
分页插件支持的数据有以下这些:
更过内容,可以参考 MP 官网连接:
https://baomidou.com/pages/97710a/#paginationinnerinterceptor
② 使用 Page 对象实现分页
@RequestMapping("/getpage")
public Object getPage(Integer pageIndex){
// 分页对象
Page page = new Page(pageIndex,10);
Page<User> result = userService.page(page);
retuern result;
}
MyBatis Plus 分页功能的底层是如何实现的?
MyBatis Plus 分页功能底层是通过拦截器来实现的,实现 MyBatis Plus 的第一步就是添加拦截器,这个拦截器就是拦截 SQL 的请求,拦截之后会对 SQL 进行动态修改(添加 limit 等分页查询操作),之后查询数据库,然后对结果进行封装,包装成分页对象,最后再将分页对象返回给客户端。
典型回答:
动态 SQL 是动态构建查询语句的机制,允许在 SQL 查询中根据不同的条件动态生成不同的 SQL 语句,以满足不同的查询需求。
动态 SQL 可以包含 条件判断、循环、参数替换等功能,使得 SQL 查询更具灵活性和可重用性。
在 MyBatis 中,动态 SQL 的主要元素:if、choose/when/otherwise、trim、where、set、foreach 等
<select id = "selectUsers" resultType = "User">
select * from users
where 1 = 1
<if test = "name != null">
and name = #{name}
if>
select>
<select id = "selectUsers" resultType = "User">
select * from users
<choose>
<when test = "name != null">
and name = #{name}
when>
<when test = "age != null">
and age = #{age}
when>
<otherwise>
and status = 'active'
otherwise>
choose>
select>
<select id = "selectUsers" resultType = "User">
select * from users
where
<trim prefix = "(" suffix =")" prefixOverrides = "and">
<if test = "name != null">
and name = #{name}
if>
<if test = "age != null">
and age = #{age}
if>
trim>
select>
<select id = "selectUsers" resultType = "User">
select * from users
where id in
<foreach collection = "idList" item = "id", open = "(" separator = "," close=")">
#{id}
foreach>
select>
这些动态 SQL 构建方式使得编写 能够根据不同条件动态生成 SQL 查询的代码 变得更加灵活和可维护。它们是 MyBatis 中的关键功能,用于处理各种复杂的查询需求。
更多详情文档,可以参考官方文档:https://mybatis.org/mybatis-3/dynamic-sql.html
典型回答:
MyBatis 二级缓存是用来提高 MyBatis 查询数据库的性能,和减少数据库访问的机制。
顾名思义,MyBatis 二级缓存中总共有两个缓存机制:
典型回答:
二级缓存默认是不开启的,手动开启 MyBatis 二级缓存步骤如下:
完整代码如下:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<mapper namespace="com.mybatis.demo.mapper.StudentMapper">
<cache/>
<select id="getStudentCount" resultType="Integer" useCache="true">
select count(*) from student
select>
mapper>
编写单元测试代码如下:
@SpringBootTest
class StudentMapperTest{
@Autowired
private StudentMapper studentMapper;
@Test
void getStudentCount(){
int count = studentMapper.getStudentCount();
System.out.println("查询结果:" + count);
int count2 = studentMapper.getStudentCount();
System.out.println("查询结果:" + coun2);
}
}
执行以上单元测试的执行结果如下:
从以上结果可以看出,两次查询虽然使用了不同的 SqlSession,但第二次查询使用了缓存,并未查询数据库。
典型回答:
MyBatis 默认不开启二级缓存原因有以下几个:
总结而言,就是分布式场景下可能会造成数据不一致问题,并且业务场景 下可能不适合等等。
典型回答:
缓存淘汰策略也叫做缓存清除策略,是当缓存数据达到最大值时,如何清除(淘汰)缓存的策略。
MyBatis 二级缓存默认写法就是(在映射文件中添加一行)
<cache/>
上述标签的效果有以下几个:
这些属性可以通过 cache 元素的属性来修改,例如下:
<cache eviction="FIFO" flushInterval="60000" size="1024"/>
这里更高级的配置创建了一个 FIFO 缓存,每隔 60s 刷新(自动清空二级缓存),最多可以存储结果对象或列表 1024 个,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
MyBatis 有哪些缓存淘汰策略
默认的缓存淘汰策略是 LRU
flushInterval(刷新间隔)默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。当设置 flushInterval后,MyBatis 会周期性地检査是否需要刷新二级缓存,避免数据过期或脏数据的问题。当超过设定的时间间隔时,MyBatis 会自动清空二级缓存,下次查询时会重新加载最新的数据到缓存中。
size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
典型回答: