代码已上传到GitHub,有兴趣的同学可以下载来看看:https://github.com/ylw-github/Java-CodeAnalysis-Demo
1. MyBatis相关核心知识
MyBatis和数据库的交互有两种方式:
- 使用传统的MyBatis提供的API;
- 使用Mapper接口;
1.1 使用Mapper接口
MyBatis 将配置文件中的每一个<mapper>
节点抽象为一个 Mapper 接口:
这个接口中声明的方法和<mapper>
节点中的<select|update|delete|insert>
节点项对应,即<select|update|delete|insert>
节点的id值为Mapper 接口中的方法名称,parameterType 值表示Mapper 对应方法的入参类型,而resultMap 值则对应了Mapper 接口表示的返回值类型或者返回结果集的元素类型。
根据MyBatis 的配置规范配置好后,通过SqlSession.getMapper(XXXMapper.class)
方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,我们使用Mapper接口的某一个方法时,MyBatis会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select("statementId",parameterObject)
;或者SqlSession.update("statementId",parameterObject)
; 等等来实现对数据库的操作,MyBatis引用Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置SQL语句,这样就可以脱离XML配置文件,实现“0配置”)。
1.2 数据处理层
数据处理层可以说是MyBatis的核心,从大的方面上讲,它要完成两个功能:
- 通过传入参数构建动态SQL语句;
- SQL语句的执行以及封装查询结果集成
List<E>
;
1.3 框架支撑层
1.3.1 事务管理机制
事务管理机制对于ORM框架而言是不可缺少的一部分,事务管理机制的质量也是考量一个ORM框架是否优秀的一个标准。
1.3.2 连接池管理机制
由于创建一个数据库连接所占用的资源比较大,对于数据吞吐量大和访问量非常大的应用而言,连接池的设计就显得非常重要。
1.3.3 缓存机制
为了提高数据利用率和减小服务器和数据库的压力,MyBatis 会对于一些查询提供会话级别的数据缓存,会将对某一次查询,放置到SqlSession 中,在允许的时间间隔内,对于完全相同的查询,MyBatis会直接将缓存结果返回给用户,而不用再到数据库中查找。
1.4 SQL语句的配置方式
传统的MyBatis 配置SQL语句方式就是使用XML文件进行配置的,但是这种方式不能很好地支持面向接口编程的理念,为了支持面向接口的编程,MyBatis 引入了Mapper接口的概念,面向接口的引入,对使用注解来配置SQL语句成为可能,用户只需要在接口上添加必要的注解即可,不用再去配置XML文件了。
但是,目前的MyBatis 只是对注解配置SQL语句提供了有限的支持,某些高级功能还是要依赖XML配置文件配置SQL 语句。
1.5 引导层
引导层是配置和启动MyBatis配置信息的方式。MyBatis 提供两种方式来引导MyBatis :
- 基于XML配置文件的方式
- 基于Java API 的方式。
1.6 主要构件及其相互关系
从MyBatis代码实现的角度来看,MyBatis的主要的核心部件有以下几个:
- SqlSession:作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能;
- Executor:MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护;
- StatementHandler:封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
- ParameterHandler:负责对用户传递的参数转换成JDBC Statement 所需要的参数;
- ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合;
- TypeHandler:负责java数据类型和jdbc数据类型之间的映射和转换;
- MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装;
- SqlSource:负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装BoundSql对象中,并返回;
- BoundSql:表示动态生成的SQL语句以及相应的参数信息;
- Configuration:MyBatis所有的配置信息都维持在Configuration对象之中;
2. 手写MyBatis代码
1.定义JDBCUtils:
package com.ylw.mybatis.orm.utils; import java.sql.*; import java.util.List; public final class JDBCUtils { private static String connect; private static String driverClassName; private static String URL; private static String username; private static String password; private static boolean autoCommit; /** 声明一个 Connection类型的静态属性,用来缓存一个已经存在的连接对象 */ private static Connection conn; static { config(); } /** * 开头配置自己的数据库信息 */ private static void config() { /* * 获取驱动 */ driverClassName = "com.mysql.jdbc.Driver"; /* * 获取URL */ URL = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8"; /* * 获取用户名 */ username = "root"; /* * 获取密码 */ password = "123456"; /* * 设置是否自动提交,一般为false不用改 */ autoCommit = false; } /** * 载入数据库驱动类 */ private static boolean load() { try { Class.forName(driverClassName); return true; } catch (ClassNotFoundException e) { System.out.println("驱动类 " + driverClassName + " 加载失败"); } return false; } /** * 专门检查缓存的连接是否不可以被使用 ,不可以被使用的话,就返回 true */ private static boolean invalid() { if (conn != null) { try { if (conn.isClosed() || !conn.isValid(3)) { return true; /* * isValid方法是判断Connection是否有效,如果连接尚未关闭并且仍然有效,则返回 true */ } } catch (SQLException e) { e.printStackTrace(); } /* * conn 既不是 null 且也没有关闭 ,且 isValid 返回 true,说明是可以使用的 ( 返回 false ) */ return false; } else { return true; } } /** * 建立数据库连接 */ public static Connection connect() { if (invalid()) { /* invalid为true时,说明连接是失败的 */ /* 加载驱动 */ load(); try { /* 建立连接 */ conn = DriverManager.getConnection(URL, username, password); } catch (SQLException e) { System.out.println("建立 " + connect + " 数据库连接失败 , " + e.getMessage()); } } return conn; } /** * 设置是否自动提交事务 **/ public static void transaction() { try { conn.setAutoCommit(autoCommit); } catch (SQLException e) { System.out.println("设置事务的提交方式为 : " + (autoCommit ? "自动提交" : "手动提交") + " 时失败: " + e.getMessage()); } } /** * 创建 Statement 对象 */ public static Statement statement() { Statement st = null; connect(); /* 如果连接是无效的就重新连接 */ transaction(); /* 设置事务的提交方式 */ try { st = conn.createStatement(); } catch (SQLException e) { System.out.println("创建 Statement 对象失败: " + e.getMessage()); } return st; } /** * 根据给定的带参数占位符的SQL语句,创建 PreparedStatement 对象 * * @param SQL * 带参数占位符的SQL语句 * @return 返回相应的 PreparedStatement 对象 */ private static PreparedStatement prepare(String SQL, boolean autoGeneratedKeys) { PreparedStatement ps = null; connect(); /* 如果连接是无效的就重新连接 */ transaction(); /* 设置事务的提交方式 */ try { if (autoGeneratedKeys) { ps = conn.prepareStatement(SQL, Statement.RETURN_GENERATED_KEYS); } else { ps = conn.prepareStatement(SQL); } } catch (SQLException e) { System.out.println("创建 PreparedStatement 对象失败: " + e.getMessage()); } return ps; } public static ResultSet query(String SQL, List<Object> params) { if (SQL == null || SQL.trim().isEmpty() || !SQL.trim().toLowerCase().startsWith("select")) { throw new RuntimeException("你的SQL语句为空或不是查询语句"); } ResultSet rs = null; if (params.size() > 0) { /* 说明 有参数 传入,就需要处理参数 */ PreparedStatement ps = prepare(SQL, false); try { for (int i = 0; i < params.size(); i++) { ps.setObject(i + 1, params.get(i)); } rs = ps.executeQuery(); } catch (SQLException e) { System.out.println("执行SQL失败: " + e.getMessage()); } } else { /* 说明没有传入任何参数 */ Statement st = statement(); try { rs = st.executeQuery(SQL); // 直接执行不带参数的 SQL 语句 } catch (SQLException e) { System.out.println("执行SQL失败: " + e.getMessage()); } } return rs; } private static Object typeof(Object o) { Object r = o; if (o instanceof java.sql.Timestamp) { return r; } // 将 java.util.Date 转成 java.sql.Date if (o instanceof java.util.Date) { java.util.Date d = (java.util.Date) o; r = new java.sql.Date(d.getTime()); return r; } // 将 Character 或 char 变成 String if (o instanceof Character || o.getClass() == char.class) { r = String.valueOf(o); return r; } return r; } public static boolean execute(String SQL, Object... params) { if (SQL == null || SQL.trim().isEmpty() || SQL.trim().toLowerCase().startsWith("select")) { throw new RuntimeException("你的SQL语句为空或有错"); } boolean r = false; /* 表示 执行 DDL 或 DML 操作是否成功的一个标识变量 */ /* 获得 被执行的 SQL 语句的 前缀 */ SQL = SQL.trim(); SQL = SQL.toLowerCase(); String prefix = SQL.substring(0, SQL.indexOf(" ")); String operation = ""; // 用来保存操作类型的 变量 // 根据前缀 确定操作 switch (prefix) { case "create": operation = "create table"; break; case "alter": operation = "update table"; break; case "drop": operation = "drop table"; break; case "truncate": operation = "truncate table"; break; case "insert": operation = "insert :"; break; case "update": operation = "update :"; break; case "delete": operation = "delete :"; break; } if (params.length > 0) { // 说明有参数 PreparedStatement ps = prepare(SQL, false); Connection c = null; try { c = ps.getConnection(); } catch (SQLException e) { e.printStackTrace(); } try { for (int i = 0; i < params.length; i++) { Object p = params[i]; p = typeof(p); ps.setObject(i + 1, p); } ps.executeUpdate(); commit(c); r = true; } catch (SQLException e) { System.out.println(operation + " 失败: " + e.getMessage()); rollback(c); } } else { // 说明没有参数 Statement st = statement(); Connection c = null; try { c = st.getConnection(); } catch (SQLException e) { e.printStackTrace(); } // 执行 DDL 或 DML 语句,并返回执行结果 try { st.executeUpdate(SQL); commit(c); // 提交事务 r = true; } catch (SQLException e) { System.out.println(operation + " 失败: " + e.getMessage()); rollback(c); // 回滚事务 } } return r; } /* * * @param SQL 需要执行的 INSERT 语句 * * @param autoGeneratedKeys 指示是否需要返回由数据库产生的键 * * @param params 将要执行的SQL语句中包含的参数占位符的 参数值 * * @return 如果指定 autoGeneratedKeys 为 true 则返回由数据库产生的键; 如果指定 autoGeneratedKeys * 为 false 则返回受当前SQL影响的记录数目 */ public static int insert(String SQL, boolean autoGeneratedKeys, List<Object> params) { int var = -1; if (SQL == null || SQL.trim().isEmpty()) { throw new RuntimeException("你没有指定SQL语句,请检查是否指定了需要执行的SQL语句"); } // 如果不是 insert 开头开头的语句 if (!SQL.trim().toLowerCase().startsWith("insert")) { System.out.println(SQL.toLowerCase()); throw new RuntimeException("你指定的SQL语句不是插入语句,请检查你的SQL语句"); } // 获得 被执行的 SQL 语句的 前缀 ( 第一个单词 ) SQL = SQL.trim(); SQL = SQL.toLowerCase(); if (params.size() > 0) { // 说明有参数 PreparedStatement ps = prepare(SQL, autoGeneratedKeys); Connection c = null; try { c = ps.getConnection(); // 从 PreparedStatement 对象中获得 它对应的连接对象 } catch (SQLException e) { e.printStackTrace(); } try { for (int i = 0; i < params.size(); i++) { Object p = params.get(i); p = typeof(p); ps.setObject(i + 1, p); } int count = ps.executeUpdate(); if (autoGeneratedKeys) { // 如果希望获得数据库产生的键 ResultSet rs = ps.getGeneratedKeys(); // 获得数据库产生的键集 if (rs.next()) { // 因为是保存的是单条记录,因此至多返回一个键 var = rs.getInt(1); // 获得值并赋值给 var 变量 } } else { var = count; // 如果不需要获得,则将受SQL影像的记录数赋值给 var 变量 } commit(c); } catch (SQLException e) { System.out.println("数据保存失败: " + e.getMessage()); rollback(c); } } else { // 说明没有参数 Statement st = statement(); Connection c = null; try { c = st.getConnection(); // 从 Statement 对象中获得 它对应的连接对象 } catch (SQLException e) { e.printStackTrace(); } // 执行 DDL 或 DML 语句,并返回执行结果 try { int count = st.executeUpdate(SQL); if (autoGeneratedKeys) { // 如果企望获得数据库产生的键 ResultSet rs = st.getGeneratedKeys(); // 获得数据库产生的键集 if (rs.next()) { // 因为是保存的是单条记录,因此至多返回一个键 var = rs.getInt(1); // 获得值并赋值给 var 变量 } } else { var = count; // 如果不需要获得,则将受SQL影像的记录数赋值给 var 变量 } commit(c); // 提交事务 } catch (SQLException e) { System.out.println("数据保存失败: " + e.getMessage()); rollback(c); // 回滚事务 } } return var; } /** 提交事务 */ private static void commit(Connection c) { if (c != null && !autoCommit) { try { c.commit(); } catch (SQLException e) { e.printStackTrace(); } } } /** 回滚事务 */ private static void rollback(Connection c) { if (c != null && !autoCommit) { try { c.rollback(); } catch (SQLException e) { e.printStackTrace(); } } } /** * 释放资源 **/ public static void release(Object cloaseable) { if (cloaseable != null) { if (cloaseable instanceof ResultSet) { ResultSet rs = (ResultSet) cloaseable; try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (cloaseable instanceof Statement) { Statement st = (Statement) cloaseable; try { st.close(); } catch (SQLException e) { e.printStackTrace(); } } if (cloaseable instanceof Connection) { Connection c = (Connection) cloaseable; try { c.close(); } catch (SQLException e) { e.printStackTrace(); } } } } }
2.定义MyInvocationHandlerMbatis
package com.ylw.mybatis.orm.mybatis.aop; import com.ylw.mybatis.orm.annotation.ExtInsert; import com.ylw.mybatis.orm.annotation.ExtParam; import com.ylw.mybatis.orm.annotation.ExtSelect; import com.ylw.mybatis.orm.utils.JDBCUtils; import com.ylw.mybatis.orm.utils.SQLUtils; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.sql.ResultSet; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; public class MyInvocationHandlerMbatis implements InvocationHandler { private Object object; public MyInvocationHandlerMbatis(Object object) { this.object = object; } // proxy 代理对象 method拦截方法 args方法上的参数值 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("使用动态代理技术拦截接口方法开始"); // 使用白话问翻译,@ExtInsert封装过程 // 1. 判断方法上是否存在@ExtInsert ExtInsert extInsert = method.getDeclaredAnnotation(ExtInsert.class); if (extInsert != null) { return extInsert(extInsert, proxy, method, args); } // 2.查询的思路 // 1. 判断方法上是否存 在注解 ExtSelect extSelect = method.getDeclaredAnnotation(ExtSelect.class); if (extSelect != null) { // 2. 获取注解上查询的SQL语句 String selectSQL = extSelect.value(); // 3. 获取方法上的参数,绑定在一起 ConcurrentHashMap<Object, Object> paramsMap = paramsMap(proxy, method, args); // 4. 参数替换?传递方式 List<String> sqlSelectParameter = SQLUtils.sqlSelectParameter(selectSQL); // 5.传递参数 List<Object> sqlParams = new ArrayList<>(); for (String parameterName : sqlSelectParameter) { Object parameterValue = paramsMap.get(parameterName); sqlParams.add(parameterValue); } // 6.将sql语句替换成? String newSql = SQLUtils.parameQuestion(selectSQL, sqlSelectParameter); System.out.println("newSQL:" + newSql + ",sqlParams:" + sqlParams.toString()); // 5.调用jdbc代码底层执行sql语句 // 6.使用反射机制实例对象### 获取方法返回的类型,进行实例化 // 思路: // 1.使用反射机制获取方法的类型 // 2.判断是否有结果集,如果有结果集,在进行初始化 // 3.使用反射机制,给对象赋值 ResultSet res = JDBCUtils.query(newSql, sqlParams); // 判断是否存在值 if (!res.next()) { return null; } // 下标往上移动移位 res.previous(); // 使用反射机制获取方法的类型 Class<?> returnType = method.getReturnType(); Object object = returnType.newInstance(); while (res.next()) { // 获取当前所有的属性 Field[] declaredFields = returnType.getDeclaredFields(); for (Field field : declaredFields) { String fieldName = field.getName(); Object fieldValue = res.getObject(fieldName); field.setAccessible(true); field.set(object, fieldValue); } // for (String parameteName : sqlSelectParameter) { // // 获取参数值 // Object resultValue = res.getObject(parameteName); // // 使用java的反射值赋值 // Field field = returnType.getDeclaredField(parameteName); // // 私有方法允许访问 // field.setAccessible(true); // field.set(object, resultValue); // } } return object; } return null; } private Object extInsert(ExtInsert extInsert, Object proxy, Method method, Object[] args) { // 方法上存在@ExtInsert,获取他的SQL语句 // 2. 获取SQL语句,获取注解Insert语句 String insertSql = extInsert.value(); // System.out.println("insertSql:" + insertSql); // 3. 获取方法的参数和SQL参数进行匹配 // 定一个一个Map集合 KEY为@ExtParamValue,Value 结果为参数值 ConcurrentHashMap<Object, Object> paramsMap = paramsMap(proxy, method, args); // 存放sql执行的参数---参数绑定过程 String[] sqlInsertParameter = SQLUtils.sqlInsertParameter(insertSql); List<Object> sqlParams = sqlParams(sqlInsertParameter, paramsMap); // 4. 根据参数替换参数变为? String newSQL = SQLUtils.parameQuestion(insertSql, sqlInsertParameter); System.out.println("newSQL:" + newSQL + ",sqlParams:" + sqlParams.toString()); // 5. 调用jdbc底层代码执行语句 return JDBCUtils.insert(newSQL, false, sqlParams); } private List<Object> sqlParams(String[] sqlInsertParameter, ConcurrentHashMap<Object, Object> paramsMap) { List<Object> sqlParams = new ArrayList<>(); for (String paramName : sqlInsertParameter) { Object paramValue = paramsMap.get(paramName); sqlParams.add(paramValue); } return sqlParams; } private ConcurrentHashMap<Object, Object> paramsMap(Object proxy, Method method, Object[] args) { ConcurrentHashMap<Object, Object> paramsMap = new ConcurrentHashMap<>(); // 获取方法上的参数 Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { Parameter parameter = parameters[i]; ExtParam extParam = parameter.getDeclaredAnnotation(ExtParam.class); if (extParam != null) { // 参数名称 String paramName = extParam.value(); Object paramValue = args[i]; // System.out.println(paramName + "," + paramValue); paramsMap.put(paramName, paramValue); } } return paramsMap; } public Object extInsertSQL() { return object; } }
3.定义SQLUtils
package com.ylw.mybatis.orm.utils; import java.util.ArrayList; import java.util.List; /** * SQL拼接<br> */ public class SQLUtils { /** * * 获取Insert语句后面values 参数信息<br> * @param sql * @return */ public static String[] sqlInsertParameter(String sql) { int startIndex = sql.indexOf("values"); int endIndex = sql.length(); String substring = sql.substring(startIndex + 6, endIndex).replace("(", "").replace(")", "").replace("#{", "") .replace("}", ""); String[] split = substring.split(","); return split; } /** * * 获取select 后面where语句 * * @param sql * @return */ public static List<String> sqlSelectParameter(String sql) { int startIndex = sql.indexOf("where"); int endIndex = sql.length(); String substring = sql.substring(startIndex + 5, endIndex); String[] split = substring.split("and"); List<String> listArr = new ArrayList<>(); for (String string : split) { String[] sp2 = string.split("="); listArr.add(sp2[0].trim()); } return listArr; } /** * 将SQL语句的参数替换变为?<br> * * @param sql * @param parameterName * @return */ public static String parameQuestion(String sql, String[] parameterName) { for (int i = 0; i < parameterName.length; i++) { String string = parameterName[i]; sql = sql.replace("#{" + string + "}", "?"); } return sql; } public static String parameQuestion(String sql, List<String> parameterName) { for (int i = 0; i < parameterName.size(); i++) { String string = parameterName.get(i); sql = sql.replace("#{" + string + "}", "?"); } return sql; } }
4.单元测试
@Test public void test() throws SQLException { UserMapper userMapper = SqlSession.getMapper(UserMapper.class); User selectUser = userMapper.selectUser("Dumas", "26"); System.out.println( "结果:" + selectUser.getNAME() + "," + selectUser.getAge()); }
运行结果:
手写MyBatis教程中,也有xml手写版,可以参考:https://blog.csdn.net/qq_35859844/article/details/86688098
总结