最近朋友小李去面试,被问到一堆关于ORM框架的问题,当场有点懵。他平时写代码用的都是MyBatis和Hibernate,但一碰到原理性问题,比如‘懒加载是怎么实现的’、‘一级缓存和二级缓存的区别’,就卡壳了。其实这种情况挺常见的,很多开发者用ORM用得顺手,但对底层机制了解不深,一到面试就吃亏。
什么是ORM?为什么用它?
ORM,全称是对象关系映射(Object-Relational Mapping),说白了就是让Java里的对象能和数据库表自动对应起来。比如你有个User类,对应数据库的user表,不用再手动拼SQL,直接调用userMapper.insert(user)就能插入数据。
好处很明显:省事、减少SQL错误、提升开发效率。就像你点外卖不用自己做饭,ORM帮你把对象“翻译”成数据库能懂的语言。
Hibernate和MyBatis有什么区别?
这是高频题。Hibernate是全自动ORM,你定义好实体类和映射关系,它自动生成SQL,连事务都给你管了。适合快速开发,但有时候生成的SQL不够优化。
MyBatis是半自动的,SQL得你自己写,但它灵活,能精细控制每一条查询。就像一个是全自动洗衣机,一个是波轮手动档——看你需要省力还是掌控感。
举个例子,复杂联表查询时,MyBatis写SQL更直观:
<select id="getUserWithOrders" resultType="map">
SELECT u.name, o.order_no
FROM user u
LEFT JOIN orders o ON u.id = o.user_id
WHERE u.id = #{userId}
</select>
懒加载是怎么实现的?
假设你查一个用户,还想关联他的订单列表,但不想一次性全查出来,这时候就可以用懒加载。Hibernate里,当你定义一个List<Order> orders字段时,可以设置fetch = FetchType.LAZY。
实际查用户的时候,并不会立刻执行订单的查询SQL,只有当你调用user.getOrders()时,才触发数据库查询。背后是用了动态代理,比如CGLIB,生成一个Orders的代理集合,等真正访问时再去加载数据。
一级缓存和二级缓存的区别
一级缓存是Session级别的,同一个会话中查两次相同的记录,第二次不会走数据库。比如:
User user1 = session.get(User.class, 1L);
User user2 = session.get(User.class, 1L);
// 第二次不会发SQL
二级缓存是SessionFactory级别的,跨Session共享。多个用户查同一个数据,可以直接从缓存拿,适合数据变动少的场景,比如字典表。但要注意并发更新问题,得配合缓存策略一起用,比如Redis做二级缓存。
MyBatis里的#{ }和${ }有什么区别?
这个几乎必考。#{ }是预编译参数占位符,会转成?,防止SQL注入。比如:
SELECT * FROM user WHERE name = #{name}
// 实际执行:WHERE name = ?
${ }是字符串替换,直接拼进SQL。比如排序字段必须用${ }:
ORDER BY ${column}
// 直接替换成 ORDER BY create_time
但用${ }要小心,如果用户输入恶意内容,可能被注入。所以能用#{ }就别用${ }。
怎么优化ORM的性能?
别光想着用,还得会调。常见做法有:避免N+1查询问题,比如查10个用户,每个用户都去查订单,就会发11条SQL。可以用JOIN FETCH或者批量加载解决。
合理使用缓存,尤其是二级缓存加Redis。还有就是SQL语句要精简,别动不动SELECT *,该建索引的字段一定要建。
最后提醒一句:面试官问ORM,不只是想听你背概念,更想看你有没有踩过坑、改过慢查询、处理过事务失效。把这些实战经验带上,通过率自然就高了。