大家好,我是南哥。
一个对Java程序员进阶成长颇有研究的人,今天我们接着新的一篇Java进阶指南。
为啥都戏称后端是CRUD Boy?难道就因为天天怼着数据库CRUD吗?要我说,是这个岗位的位置要的就是你CRUD,你不得不CRUD。哪有公司天天能给你搭建高并发、高可用、大数据框架的活呢,一条业务线总要成长吧,慢慢成熟了就要装修工来缝缝补补、美化美化,也就是CRUD的活。
不能妄自菲薄CRUD Boy,我们是后端工程师。今天来指南下操作数据库之MyBatis框架。
本文收录在我开源的《Java学习面试指南》中,一份覆盖Java程序员所需掌握核心知识、面试重点的Java指南,目前已经更新到近200道面试官必考的面试题。希望收到大家的 ⭐ Star ⭐支持。GitHub地址:https://github.com/hdgaadd/JavaGetOffer,相信你看了一定不会后悔。
面试官:你说下对MyBatis的理解?
如果没有MyBatis的支持,大家是怎么实现通过程序控制数据库的?首先我们需要为程序引入MySQL连接依赖mysql-connector.jar
,加载数据库JDBC驱动,接着创建数据库连接对象Connection
、SQL语句执行器Statement
,再把SQL语句发送到MySQL执行,最后关闭SQL语句执行器和数据库连接对象。
整个过程是比较繁琐的,这是通过JDBC操作MySQL必走的过程。可实际开发可给不了你那么多时间,如果大家非要用JDBC去写大量的冗余代码也可以,能抗住催你开发进度的压力就行。
这是JDBC操作的过程。
public class JDBCController {
private static final String db_url = "jdbc:mysql://localhost:3306/db_user";
private static final String user = "root";
private static final String password = "root";
public static void main(String[] args) {
Connection connection = null;
Statement statement = null;
String sql = "select * from user order by id desc";
try {
connection = DriverManager.getConnection(db_url, user, password);
statement = connection.createStatement();
int result = statement.executeUpdate(sql);
System.out.println(result);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}
}
}
MyBatis能帮助我们什么?早在2002年,MyBatis的前身iBatis诞生,并于2010年改名为MyBatis。该框架引入了SQL映射作为持久层开发的一种方法,也就是说我们不需要把SQL耦合在代码里,只需要把SQL语句单独写在XML配置文件中。
以下是MyBatis编写SQL的写法。SQL的编写已经和程序运行分离开,消除了大量JDBC冗余代码,同时MyBatis还能和Spring框架集成。整个SQL编写的流程变得更加灵活也更加规范化。
@Mapper
public interface UserMapper extends BatchMapper<UserDO> {
List<UserDO> selectAllUser();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.JavaGetOffer.UserDO">
<select id="selectAllUser" resultType="org.JavaGetOffer.UserDO">
select * from user order by id desc
</select>
</mapper>
面试官:那SqlSession知道吧?
从我们偷偷访问某个小网站开始,到我们不耐烦地关闭浏览器或者退出登录时,我们作为用户和网站的一次会话就结束了。MyBaits框架要访问数据库同样要与数据库建立通信桥梁,而SqlSession对象表示的就是MyBaits框架与数据库建立的会话。
我们可以利用SqlSession来操作数据库,如下代码。
@Test
public void testMybatis() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<UserDO> userList = userMapper.listAllUser();
System.out.println(JSON.toJSONString(userList));
}
面试官:Mybatis的缓存有哪几种?
软件系统合理使用缓存有一个好处。有了缓存,在原始数据没有更新的情况下,我们不需要重新再去获取一遍数据,这也减少了数据库IO,达到提升数据库性能的目的。
MyBatis同样提供了两个级别的缓存,一级缓存是基于上文提到的SqlSession实现,二级缓存是基于Mapper实现。
一级缓存作用在同一个SqlSession对象中,当SqlSession对象失效则一级缓存也跟着失效。我们梳理下一级缓存的生命周期。首先第一次查询时会把查询结果写入SqlSession缓存,如果第二次查询时原始数据没有改变则会读取缓存,但如果是修改、删除、添加语句的执行,那SqlSession缓存会被全部清空掉,这也是为了防止脏读的出现。
一级缓存缓存底层使用的是一个简单的Map数据结构来存储缓存,其中key为SQL + 参数
、val为查询结果集
。一级缓存的生命周期如下。
二级缓存的作用域是同一个命名空间namespace的Mapper对象,也就是说同一个Mapper下的多个SqlSession是可以共用二级缓存的。二级缓存的缓存写入、清空流程和一级缓存相似,但二级缓存的生命周期是和应用程序的生命周期一致的。为什么?因为Mybatis框架与Spring IOC集成的Mapper对象是单例对象。
另外大家还需要注意下,Mybatis的一级缓存是默认开启的且不能关闭,而二级缓存则需要我们手动开启,我们需要在配置文件中配置cacheEnabled
参数。
<configuration>
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
面试官:那Mybatis缓存有什么问题吗?
缓存是好,就是问题有点多,目前大厂大都禁止了Mybatis缓存的使用。
南哥总结了下,主要有以下原因。
(1)适用场景少
Mybatis二级缓存更适用于读多写少的业务场景,但是对于细粒度的缓存支持并不友好。举个用烂了的商城例子,每个商品信息的更新是非常频繁的的,而让用户每次都看到的是最新的商品信息又非常重要。
在同一个namespace的Mapper中一般会包含多个商品信息的二级缓存,只要有某一个商品信息更新了,则所有商品缓存都会全部失效。那其实在这个业务场景中,二级缓存的存在已经没有多大必要了,还反而增加了系统复杂性。
(2)数据不一致性问题
如果多个不同namespace的Mapper都共同操作同一个数据库表的情况下,第一个Mapper更新了数据库表会清空它本身的二级缓存,但其他namespace的Mapper是没有感知的,仍然缓存的是旧数据,数据不一致的问题就出现了。
(3)不适用于分布式系统
现在还用单机部署的业务已经不多了,大家都紧跟潮流搭了个分布式、高可用的系统。在分布式系统中,如果每个节点都使用自己的本地缓存,假如现在节点A更新了缓存,但节点B、节点C是不会进行同步更新的,同样产生了数据不一致的问题。
面试官:Mybatis分页插件是怎么实现的?
Mybatis分页的原理其实很简单,没有想象的那么复杂。我们只需要拦截SQL查询语句,再把SQL语句作为子查询,外面包裹一层SELECT * FROM
后再加上LIMIT
的分页约束语句。
如下SQL示例,确实挺简单的。
SELECT * FROM user
SELECT u.* FROM (SELECT * FROM user) u LIMIT M, N
我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!
创作不易,不妨点赞、收藏、关注支持一下,各位的支持就是我创作的最大动力❤️
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。