Java 反射与 MyBatis:ORM 框架的 “灵魂基石”
目录
一、ORM框架
1.1 什么是 ORM?
1.2 反射在 ORM 中的角色
二、简化版 MyBatis:反射实现 ORM 核心逻辑
2.1 数据库实体类(与表映射)
2.2 数据库操作基础类(Dao)
2.3 反射核心:SqlSession类(模拟 MyBatis 的SqlSession)
2.4 测试类(Test)
2.5 运行结果
三、案例解析
3.1 类名与表名的映射
3.2 属性与字段的映射
3.3 动态 SQL 生成
3.4 解耦与扩展性
在 Java 持久层框架中,MyBatis 作为经典的 ORM(Object-Relational Mapping,对象 - 关系映射)框架,让我们可以通过面向对象的方式操作数据库。而这一切的 “魔法”,离不开 Java 反射机制的支撑。本篇博客将结合简化版 MyBatis 案例,深入解析反射在 ORM 中的核心作用。
一、ORM框架
1.1 什么是 ORM?
ORM 的核心是 “对象 - 表映射”:将 Java 对象的属性与数据库表的字段一一对应,从而让开发者可以通过操作对象,间接完成对数据库的增删改查。简单来说,ORM框架的作用就是:
-
将Java对象映射成数据库中的表(Java 对象
Emps
→ 对应数据库表t_emps
) -
将对象属性映射成数据库表的字段(对象属性
ename
→ 对应表字段ename
) -
将对象操作映射成SQL语句(调用
emp.setEname("张三")
→ 等价于执行UPDATE t_emps SET ename='张三'...
)
1.2 反射在 ORM 中的角色
ORM 要实现 “对象→表” 的自动映射,必须在运行时动态获取对象的元数据(如类名、属性名、方法等),而这正是 Java 反射的核心能力:
-
动态获取类名 → 推断数据库表名(如
Emps
→t_emps
)。 -
动态获取属性名 → 推断数据库字段名(如
ename
→ename
)。 -
动态调用 get/set 方法 → 读取 / 设置对象属性,进而生成 SQL 或封装查询结果。
二、简化版 MyBatis:反射实现 ORM 核心逻辑
下面通过一个简化案例,模拟 MyBatis 如何利用反射实现 “对象→SQL” 的自动转换。
2.1 数据库实体类(与表映射)
映射关系:Emps
类 → 数据库表t_emps
;类属性 → 表字段(如ename
→ ename
)。
package com.demo1;public class Emps {private int eid;private String ename;private String epwd;private String ebirthday;private double esalary;private String eaddress;private int estate;public int getEid() {return eid;}public void setEid(int eid) {this.eid = eid;}public String getEname() {return ename;}public void setEname(String ename) {this.ename = ename;}public String getEpwd() {return epwd;}public void setEpwd(String epwd) {this.epwd = epwd;}public String getEbirthday() {return ebirthday;}public void setEbirthday(String ebirthday) {this.ebirthday = ebirthday;}public double getEsalary() {return esalary;}public void setEsalary(double esalary) {this.esalary = esalary;}public String getEaddress() {return eaddress;}public void setEaddress(String eaddress) {this.eaddress = eaddress;}public int getEstate() {return estate;}public void setEstate(int estate) {this.estate = estate;}}
2.2 数据库操作基础类(Dao
)
负责 JDBC 连接与 SQL 执行:
package com.demo1;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;import com.mysql.cj.xdevapi.PreparableStatement;public class Dao {Connection conn;public Dao() {try {Class.forName("com.mysql.cj.jdbc.Driver");conn = DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/jk202508", "root", "152602");} catch (ClassNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public void closeDB() {if (null != conn) {try {conn.close();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();}}}public void addData(String sql) {try {PreparedStatement pstmt = conn.prepareStatement(sql);pstmt.executeUpdate();} catch (SQLException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {this.closeDB();}}}
2.3 反射核心:SqlSession
类(模拟 MyBatis 的SqlSession
)
通过反射自动生成 INSERT SQL,并执行:
反射关键操作:
-
Class对象.getSimpleName()
:获取类名(如Emps
)。 -
Class对象.getDeclaredMethods()
:获取所有方法(包括私有方法)。 -
method.invoke(obj)
:调用对象的方法,动态获取属性值。
package com.demo1;import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;public class SqlSession {/*** 自动生成INSERT SQL并执行* 参数为obj: 要插入的Java对象*/public void addObject(Object obj) throws Exception {// 核心的就是:反射String sql = "insert into ";// 获取表名,通过类名去获取 假设所有表明都以"t_"开头Class c = obj.getClass();String className = c.getSimpleName();System.out.println(className);sql += "t_" + className.toLowerCase() + "(";
// System.out.println(sql);// 声明一个集合放入列名List<String> columns = new ArrayList<String>();// 声明一个集合,存放数据库表中的值List<Object> values = new ArrayList<Object>();// 获取列名,通过反射获取get方法,get方法可以获取列名,可以获取值Method[] ms = c.getDeclaredMethods();for (Method m : ms) {String methodName = m.getName();
// System.out.println("所有的方法名为: " + methodName);if (methodName.startsWith("get")) {
// System.out.println("方法名为: " + methodName);String columnName = methodName.substring(3).toLowerCase();// 假设id是自增长的,不需要去设置if (!columnName.contains("id")) {System.out.println(columnName);columns.add(columnName); // 把列名放入集合中Object abjValue = m.invoke(obj, null); // 调用getter方法获取属性值if (abjValue instanceof String) {values.add("'" + abjValue + "'"); // 把值放入集合中}else {values.add(abjValue);}}}}// 把列名拼进sqlfor (int i = 0; i < columns.size(); i++) {if (i == columns.size() - 1) {sql += columns.get(i) + ")";} else {sql += columns.get(i) + ",";}}sql += " values(";// 把值拼进sqlfor (int i = 0; i < values.size(); i++) {if (i == values.size() - 1) {sql += values.get(i) + ")";} else {sql += values.get(i) + ",";}}System.out.println(sql);// 将sql语句传给Dao类去执行Dao dao = new Dao();dao.addData(sql);}}
2.4 测试类(Test
)
package com.demo1;public class Test {public static void main(String[] args) {Emps emp = new Emps();emp.setEname("花花");emp.setEpwd("123456");emp.setEbirthday("2004-9-9");emp.setEaddress("成都");emp.setEsalary(10000.90);emp.setEstate(1);// 一个对象和数据库的一张表对应SqlSession sqlSession = new SqlSession();try {sqlSession.addObject(emp);System.out.println("插入成功!");} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}
2.5 运行结果
三、案例解析
3.1 类名与表名的映射
通过Class对象.getSimpleName()
获取类名(如Emps
),再约定 “表名 = t_ + 类名小写”,实现类→表的自动映射。
3.2 属性与字段的映射
通过反射遍历所有get
方法:
-
方法名
getEname
→ 截取后得到字段名ename
。 -
调用
getEname()
→ 动态获取属性值“花花”。
从而实现属性→字段的自动映射。
3.3 动态 SQL 生成
基于反射收集的 “字段名” 和 “属性值”,自动拼接出完整的insert语句:
insert into t_emps(epwd,ename,esalary,eaddress,estate,ebirthday) values('123456','花花',10000.9,'成都',1,'2004-9-9')
3.4 解耦与扩展性
如果新增一个Stus
类(对应t_stus
表),无需修改SqlSession
代码,直接调用sqlSession.addObject(s)
即可,体现了 ORM“配置化、可扩展” 的核心优势 —— 这一切都依赖反射的 “动态性”。
Stus
类:
package com.demo1;public class Stus {private int sid;private String sname;private String saddress;public int getSid() {return sid;}// public void setSid(int sid) {
// this.sid = sid;
// }public String getSname() {return sname;}public void setSname(String sname) {this.sname = sname;}public String getSaddress() {return saddress;}public void setSaddress(String saddress) {this.saddress = saddress;}}
Test类:
package com.demo1;public class Test {public static void main(String[] args) {Stus s = new Stus();s.setSname("QQ");s.setSaddress("南京");Emps emp = new Emps();emp.setEname("花花");emp.setEpwd("123456");emp.setEbirthday("2004-9-9");emp.setEaddress("成都");emp.setEsalary(10000.90);emp.setEstate(1);// 一个对象和数据库的一张表对应SqlSession sqlSession = new SqlSession();try {sqlSession.addObject(s);
// sqlSession.addObject(emp);System.out.println("插入成功!");} catch (Exception e) {// TODO Auto-generated catch blocke.printStackTrace();}}}