有次我去面試,我人都傻了
“用過 MyBatis 嗎?”
“用過!”
“那說說它怎么把接口變成 SQL 的?”
“……”
我當(dāng)場裂開。
1. 日常場景
大多數(shù)開發(fā)都是下面這個流程:
Controller → Service → Mapper → XML
寫多了,就以為MyBatis
只是:
直到面試官問:“接口里可沒SQL
,怎么就跑起來了?”,我才清醒的知道它沒那么簡單。
2. 案例
需求:查用戶信息。
1. 先整接口
public interface UserMapper {
User selectById(Long id);
}
2. 再整XML
<select id="selectById" resultType="com.xxx.User">
SELECT * FROM t_user WHERE id = #{id}
</select>
3. Service里直接調(diào)
@Autowired
private UserMapper userMapper;
public User get(Long id) {
return userMapper.selectById(id);
}
一切看起來歲月靜好,直到面試官問:“userMapper
是個接口,Spring
怎么給你塞了個能跑的對象?”,這話一出,直接懵了。
4. 答案在這
1. 接口咋變成對象?
答案:MyBatis
用JDK
動態(tài)代理給你整了一個代理對象。
- · 代理對象攔著你的調(diào)用,再去找
XML
里的SQL
代碼片段:
UserMapper mapper = (UserMapper) Proxy.newProxyInstance(
UserMapper.class.getClassLoader(),
new Class[]{UserMapper.class},
new MapperProxy(sqlSession)
);
2. SQL什么時候拼?
代理對象收到調(diào)用后,干了兩件小事:
- 1. 根據(jù)方法名+參數(shù),找到
XML
里那條SQL
- 2. 把
#{}
換成真正的?
,再把參數(shù)塞進(jìn)去
也就是SqlSource
→BoundSql
的過程。
3. 結(jié)果怎么塞進(jìn)實(shí)體?
MyBatis
偷偷調(diào)了:
ResultSet → 反射 → User 對象
全程靠ResultSetHandler
干活,字段名對上就行,對不上就用@Results
手動指。
5. 手寫一個極簡 MyBatis
1. 定義注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
String value();
}
2. 接口
public interface UserMapper {
@Select("SELECT * FROM t_user WHERE id = #{id}")
User selectById(Long id);
}
3. 代理類
public class MyMapperProxy implements InvocationHandler {
private Connection conn;
public MyMapperProxy(Connection conn) {
this.conn = conn;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Select select = method.getAnnotation(Select.class);
String sql = select.value();
sql = sql.replace("#{id}", args[0].toString());
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery();
if (rs.next()) {
User u = new User();
u.setId(rs.getLong("id"));
u.setName(rs.getString("name"));
return u;
}
return null;
}
}
4. 啟動器
Connection conn = DriverManager.getConnection("jdbc:mysql://...", "root", "root");
UserMapper mapper = (UserMapper) Proxy.newProxyInstance(
UserMapper.class.getClassLoader(),
new Class[]{UserMapper.class},
new MyMapperProxy(conn)
);
System.out.println(mapper.selectById(1L));
跑起來,控制臺真的打出了用戶信息。
6. 更多知識點(diǎn)
- · 一級緩存:同一個
SqlSession
,第二次查直接拿內(nèi)存 - · 二級緩存:跨
SqlSession
,但要配 <cache/>
- · 延遲加載:查用戶不查部門,用到部門再發(fā)
SQL
若是面試能聊到這里,基本已經(jīng)反殺了。
7. 總結(jié)
接口 + 代理 → 找SQL
→ 拼參數(shù) → 反射塞結(jié)果。
08 彩蛋:面試現(xiàn)場原對話還原
面試官:“為啥#{}
能防SQL
注入?”
我:“因?yàn)樗日嘉唬僭O(shè)參數(shù),JDBC
會當(dāng)字符串處理。”
面試官:“那${}
呢?”
我:“直接拼,容易出事,一般不這樣寫。”
面試官:“行,過了。”
我:???
我是大華,專注分享前后端開發(fā)的實(shí)戰(zhàn)筆記。關(guān)注我,少走彎路,一起進(jìn)步!