一、Mybatis 流程简介
最近在看 Mybatis 的源码,大致了解整个框架流程后便手写了一个特别简单的SimpMybatis
的小Demo,来巩固这整个框架的学习。下图是我所画的框架大致执行流程:
💊对上图分析后得出结论:
- Mybatis 的配置文件分为两种,且这两个配置文件会被封装到 Configuration 中
- 主配置文件(MybatisConfig.
- 映射文件(xxxMapper.
- 通过 Mybatis 配置文件得到 SqlSessionFactory ;
- 通过 SqlSessionFactory 得到 SqlSession,它就相当于 Request 请求;
- SqlSession 调用底层的 Executor 执行器来操作数据库,同时执行器有两类实现
- 基本实现
- 带有缓存功能的实现
- 解析传入的参数,对其进行封装,执行并返回结果;
以上就是我梳理的 Mybatis 大致流程,看似简单,却很精妙。
二、手写简化版 Mybatis 设计思路
2.1 简化后的思路
2.3 创建SqlSession,搭建 Configuration 和 Executor 之间的桥梁
从流程图中的箭头可以看出,MySqlSession 的成员变量中必须得有 MyExecutorImpl 和 MyConfig 去集中做调配。一个Session仅拥有一个对应的数据库连接。类似于一个前段请求Request,它负责直接调用对应 execute(sql) 来做 CRUD 操作。
2.4 创建 MyExecutor,封装 JDBC 操作数据库
MyExecutor 是一个执行器,负责SQL语句的生成和查询缓存的维护,也就是 Jdbc 的代码将在这里完成,不过本文只实现了单表,查询缓存并未实现。
2.5 创建 MySqlSessionProxy,使用动态代理生成 Mapper 对象
只是希望对指定的接口生成一个对象,使得执行它的时候能运行一句 sql,而接口无法直接调用方法,所以这里使用动态代理生成对象,在执行时还是回到 MySqlSession 中调用查询,最终由 MyExecutorImpl 做 JDBC查询。这样设计是为了单一职责,可扩展性更强。
三、实现自己的Mybatis
这次会将其打成 Jar 包,并将其导入项目实现,做一个 Mybatis 的还原。
工程文件及目录:
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:17 */public class MyConfig { /** * 启动应用程序类加载器 */ private static final ClassLoader loader = ClassLoader.getSystemClassLoader(); /** * 数据库建立连接 * @return 返回数据库连接对象 * */ public Connection build() { // Mybatis主配置文件名 String resource = "mybatis-config.⚡由 MyConfig类 代码可以得知:
- Mybatis 主配置类名称必须为:
mybatis-config.;
- mybatis-config.<database></database>
;
⚡由 MyConfig类 代码可以得知:
- Mybatis 主配置类名称必须为:
mybatis-config.;
- mybatis-config.<database></database>
resultType
(个人偷懒,没做判断);3.3 MySqlSession 代理
MySqlSession 肯定不会自己去执行,因为不能写死所以使用动态代理来使代理类去实现具体方法。
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:52 */public class MySqlSession { private final MyExcutor excutor= new MyExcutorImpl(); private final MyConfig config = new MyConfig(); public <T> T selectValue(Mapping statement, List<Object> parameter){ return excutor.queryValue(statement, parameter); } public <T> T selectNull(Mapping statement){ return excutor.queryNull(statement); } public int deleteValue(Mapping statement, List<Object> parameter) { return excutor.deleteValue(statement, parameter); } public int updateValue(Mapping statement, List<Object> parameter) { return excutor.updateValue(statement, parameter); } public int insertValue(Mapping mapping, List<Object> parameter) { return excutor.insertValue(mapping, parameter); } @SuppressWarnings("unchecked") public <T> T getMapper(Class<T> clas){ //动态代理调用 return (T) Proxy.newProxyInstance(clas.getClassLoader(),new Class[]{clas}, new MySqlSessionProxy(config,this)); }}
编写代理类,把mapper映射文件解析进来
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:55 */public class MySqlSessionProxy implements InvocationHandler { private MyConfig config; private MySqlSession sqlSession; public MySqlSessionProxy(MyConfig config, MySqlSession sqlSession) { this.config = config; this.sqlSession = sqlSession; } @Override public Object invoke(Object proxy, Method method,Object[] args) { String name = method.getDeclaringClass().getName(); String mapperName = name.substring(name.lastIndexOf(".")+1); MappingBean bean=config.parseMapper(MyConfig.parse
⚡注意:通过上段代码可知,映射文件必须和接口名称保持一致。
a. 接口实体类/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:38 */public class MappingBean { /** * 接口名 */ private String interfaceName; /** * 接口下所有方法 */ private List<Mapping> list; // setter、getter略}
b. 映射文件中 Sql 的实体类
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:38 */public class Mapping { private String sqlType; private String funcName; private String sql; private Object resultType; private String parameterType; // setter、getter略}
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:38 */public class MappingBean { /** * 接口名 */ private String interfaceName; /** * 接口下所有方法 */ private List<Mapping> list; // setter、getter略}
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:38 */public class Mapping { private String sqlType; private String funcName; private String sql; private Object resultType; private String parameterType; // setter、getter略}
3.5 创建 MyExcutor 接口以及实现类
MyExcutor 接口
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:42 */public interface MyExcutor { // 无参查询 <T> T queryNull(Mapping mapping); // 有参查询 <T> T queryValue(Mapping mapping, List<Object> params); // 删除 int deleteValue(Mapping mapping, List<Object> params); // 更新 int updateValue(Mapping mapping, List<Object> params); // 插入 int insertValue(Mapping mapping, List<Object> params);}
MyExcutorImpl 实现类
这里通过反射将结果转换成对象
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 12:42 */public class MyExcutorImpl implements MyExcutor { private MyConfig config = new MyConfig(); @Override public <T> T queryNull(Mapping mapping) { Connection conn = config.build(); PreparedStatement preparedStatement; ResultSet resultSet; Object obj; List<Object> list = new ArrayList<>(); try { preparedStatement=conn.prepareStatement(mapping.getSql()); if (mapping.getResultType() == null){ throw new RuntimeException("返回的映射结果不能为空!"); } resultSet = preparedStatement.executeQuery(); int row = 0; ResultSetMetaData rd = resultSet.getMetaData(); while (resultSet.next()){ obj=resultToObject(resultSet,mapping.getResultType()); row++; list.add(obj); } System.out.println("记录行数:"+row); } catch (SQLException e) { e.printStackTrace(); } return (T) list; } @Override public <T> T queryValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); PreparedStatement preparedStatement; ResultSet resultSet; Object obj; List<Object> list = new ArrayList<>(); try { preparedStatement=conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } if (mapping.getResultType() == null){ throw new RuntimeException("返回的映射结果不能为空!"); } resultSet = preparedStatement.executeQuery(); int row = 0; ResultSetMetaData rd = resultSet.getMetaData(); while (resultSet.next()){ obj=resultToObject(resultSet,mapping.getResultType()); row++; list.add(obj); } System.out.println("记录行数:"+row); } catch (SQLException e) { e.printStackTrace(); } return (T) list; } @Override public int deleteValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); int rows = 0; PreparedStatement preparedStatement=null; try { preparedStatement = conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } rows = preparedStatement.executeUpdate(); if (rows != 0) { System.out.println("删除成功,受影响行数:"+rows); } else { System.out.println("删除失败,数据库无相应数据..."); } } catch (SQLException e) { e.printStackTrace(); } return rows; } @Override public int updateValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); int rows = 0; PreparedStatement preparedStatement=null; try { preparedStatement = conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } rows = preparedStatement.executeUpdate(); if (rows != 0) { System.out.println("修改成功,受影响行数:"+rows); } else { System.out.println("修改失败,数据库无相应数据..."); } } catch (SQLException e) { e.printStackTrace(); } return rows; } @Override public int insertValue(Mapping mapping, List<Object> params) { Connection conn = config.build(); int rows = 0; PreparedStatement preparedStatement=null; try { preparedStatement = conn.prepareStatement(mapping.getSql()); for (int i=0; i<params.size(); i++) { preparedStatement.setString(i+1, params.get(i).toString()); } try { rows = preparedStatement.executeUpdate(); if (rows != 0) { System.out.println("插入成功,受影响行数:"+rows); } else { System.out.println("插入失败..."); } } catch (SQLException throwables) { throw new RuntimeException("插入重复 \"Key\" 值数据"); } } catch (SQLException e) { e.printStackTrace(); } return rows; } private <T> T resultToObject(ResultSet rs, Object object) { Object obj=null; try { Class<?> cls = object.getClass(); /* 这里为什么要通过class再new一个对象? 因为如果不new一个新的对象,每次返回的都是形参上的object, 而这个object都是同一个,会导致list列表后面覆盖前面值。 */ obj=cls.newInstance(); //获取结果集元数据(获取此 ResultSet 对象的列的编号、类型和属性。) ResultSetMetaData rd=rs.getMetaData(); for (int i = 0; i < rd.getColumnCount(); i++) { //获取列名 String columnName=rd.getColumnLabel(i+1); //组合方法名 String methodName="set"+columnName.substring(0, 1).toUpperCase()+columnName.substring(1); //获取列类型 int columnType=rd.getColumnType(i+1); Method method=null; switch(columnType) { case java.sql.Types.VARCHAR: case java.sql.Types.CHAR: method=cls.getMethod(methodName, String.class); method.invoke(obj, rs.getString(columnName)); break; case java.sql.Types.INTEGER: method=cls.getMethod(methodName, Integer.class); method.invoke(obj, rs.getInt(columnName)); break; default: break; } } } catch (IllegalAccessException | InstantiationException | NoSuchMethodException | InvocationTargetException | SQLException e) { e.printStackTrace(); } return (T) obj; }}
四、打包测试
4.1 将其打成 Jar 包
4.2 创建一个Maven项目,因为需要导入对应的包
<!-- https://mvnrepository.com/artifact/org.dom4j/dom4j --><!--
⚡Maven导入本地Jar包方法自行百度,这里就不赘述。
4.3 创建数据库
这里提供一个我测试的,你们可以自行创建其他的
CREATE DATABASE IF NOT EXISTS `test`;USE `test`;CREATE TABLE `user` ( `id` INT ( 10 ) NOT NULL, `sex` VARCHAR ( 2 ) NOT NULL, `password` VARCHAR ( 255 ) DEFAULT NULL, `username` VARCHAR ( 255 ) DEFAULT NULL, PRIMARY KEY ( `id` ) ) ENGINE = INNODB AUTO_INCREMENT = 2 DEFAULT CHARSET = utf8;INSERT INTO `test`.`user` ( `id`, `sex`, `password`, `username` )VALUES ( 1, '男', '12344', '五六' ), ( 2, '女', '12643', '张三' ), ( 3, '男', '1245453', '李四' );
4.4 创建实体类
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 16:17 */public class User { private Integer id; private String sex; private String password; private String username; // setter、getter略}
4.5 创建 UserMapper 接口
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 16:17 */public interface UserMapper { List<User> getUsers(); List<User> getUserBySexAndName(String sex, String username); int deleteUserById(Integer id); int updateUserByName(String username, String password); int insertUser(int id, String sex, String password, String username);}
<?4.8 创建启动类测试
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 16:24 */public class app { public static void main(String[] args) { MySqlSession sql = new MySqlSession(); UserMapper mapper = sql.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); users.forEach(System.out::println); System.out.println("=========================="); List<User> users1 = mapper.getUserBySexAndName("女", "张三"); users1.forEach(System.out::println); System.out.println("=========================="); mapper.deleteUserById(1); System.out.println("=========================="); mapper.updateUserByName("五六", "女"); System.out.println("=========================="); mapper.insertUser(10, "男", "123123", "五七"); }}
4.9 测试结果
👌测试成功,这就是本人所手写的Mybatis,虽然比较简单,但还是学习到了很多东西。
💥项目放在 Gitee 上有需要自行下载,觉得可以还请点个Star
原文转载:http://www.shaoqun.com/a/656739.html
aca:https://www.ikjzd.com/w/1371
笨鸟转运:https://www.ikjzd.com/w/1550
一、Mybatis流程简介最近在看Mybatis的源码,大致了解整个框架流程后便手写了一个特别简单的SimpMybatis的小Demo,来巩固这整个框架的学习。下图是我所画的框架大致执行流程:💊对上图分析后得出结论:Mybatis的配置文件分为两种,且这两个配置文件会被封装到Configuration中主配置文件(MybatisConfig.映射文件(xxxMapper.通过Mybatis配置文
patents:https://www.ikjzd.com/w/857
c88:https://www.ikjzd.com/w/1017.html
芒果店长:https://www.ikjzd.com/w/1533
小三问老公她的胸部手感好不好:http://lady.shaoqun.com/a/272445.html
老公深夜归来诸多细节暴露出轨:http://lady.shaoqun.com/a/270392.html
阿里国际站推买家"承诺达":23国5日达:https://www.ikjzd.com/home/106423
4.8 创建启动类测试
/** * @author Kenelm * @version 1.0 * @date 2020/11/22 16:24 */public class app { public static void main(String[] args) { MySqlSession sql = new MySqlSession(); UserMapper mapper = sql.getMapper(UserMapper.class); List<User> users = mapper.getUsers(); users.forEach(System.out::println); System.out.println("=========================="); List<User> users1 = mapper.getUserBySexAndName("女", "张三"); users1.forEach(System.out::println); System.out.println("=========================="); mapper.deleteUserById(1); System.out.println("=========================="); mapper.updateUserByName("五六", "女"); System.out.println("=========================="); mapper.insertUser(10, "男", "123123", "五七"); }}
4.9 测试结果
👌测试成功,这就是本人所手写的Mybatis,虽然比较简单,但还是学习到了很多东西。
💥项目放在 Gitee 上有需要自行下载,觉得可以还请点个Star
原文转载:http://www.shaoqun.com/a/656739.html
aca:https://www.ikjzd.com/w/1371
笨鸟转运:https://www.ikjzd.com/w/1550
一、Mybatis流程简介最近在看Mybatis的源码,大致了解整个框架流程后便手写了一个特别简单的SimpMybatis的小Demo,来巩固这整个框架的学习。下图是我所画的框架大致执行流程:💊对上图分析后得出结论:Mybatis的配置文件分为两种,且这两个配置文件会被封装到Configuration中主配置文件(MybatisConfig.映射文件(xxxMapper.通过Mybatis配置文
patents:https://www.ikjzd.com/w/857
c88:https://www.ikjzd.com/w/1017.html
芒果店长:https://www.ikjzd.com/w/1533
小三问老公她的胸部手感好不好:http://lady.shaoqun.com/a/272445.html
老公深夜归来诸多细节暴露出轨:http://lady.shaoqun.com/a/270392.html
阿里国际站推买家"承诺达":23国5日达:https://www.ikjzd.com/home/106423
没有评论:
发表评论