• SpringBoot中间件—ORM(Mybatis)框架实现


    目录

    定义

    需求背景

    方案设计

    代码展示

    UML图

     实现细节

    测试验证

     总结


    源码地址(已开源)https://gitee.com/sizhaohe/mini-mybatis.git  跟着源码及下述UML图来理解上手会更快,拒绝浮躁,沉下心来搞

    定义:

            ORM:Object Relational Mapping  -->  对象关系映射,是一种程序设计技术,用于实现面向对象编程语言里面不同类型系统的数据之间的转换

    需求背景:

            记不记得刚开始学JAVA时,编写一大串JDBC相关代码来进行与数据库的交互,日后我们接触到的MyBatis、MyBatisPlus等都是使用ORM组件来实现的框架。          

            本篇文章提炼出mybatis【最】经典、【最】精简、【最】核心的代码设计,来实现一个【mini-mybatis】,从而熟悉并掌握ORM框架的涉及实现。

    方案设计:

            

    •  中间的四部分处理是ORM框架的核心内容
    • 这个框架会提供出SqlSession工厂以及调用方式

    代码展示

    UML图

    很重要,建议code前跟我一样,先将类UML图整理出来,整个类的依赖关系及代码执行流程会一目而然。

    篇幅有限,展开观看

    • 以上为ORM框架实现核心类:加载mysql配置文件、对mapper-xml解析、获取数据库session、操作数据库及封装响应结果。

     实现细节

    1.定义sqlsession接口

    对数据库的定义和处理,本篇我们只封装一个 T selectOne(Object param);

    1. public interface SqlSession {
    2. T selectOne(String statement, Object parameter);
    3. void close();
    4. }

    2.DefaultSqlSession(SqlSession的实现)

    使用rt.jar包下(java.lang.sql包下)

    Connection接口(负责与数据库进行连接)及PreparedStatement(执行具体sql)接口来实现

    1. public class DefaultSqlSession implements SqlSession{
    2. private Connection connection;
    3. private Map mapperElement;
    4. public DefaultSqlSession(Connection connection, Map mapperElement) {
    5. this.connection = connection;
    6. this.mapperElement = mapperElement;
    7. }
    8. @Override
    9. public T selectOne(String statement, Object parameter) {
    10. XNode xNode = mapperElement.get(statement);
    11. Map parameterMap = xNode.getParameter();
    12. try {
    13. PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
    14. buildParameter(preparedStatement, parameter, parameterMap);
    15. // SQL执行结果集的行数据
    16. ResultSet resultSet = preparedStatement.executeQuery();
    17. List objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
    18. return objects.get(0);
    19. } catch (Exception e) {
    20. e.printStackTrace();
    21. }
    22. return null;
    23. }
    24. private List resultSet2Obj(ResultSet resultSet, Class clazz) {
    25. List list = new ArrayList<>();
    26. try {
    27. ResultSetMetaData metaData = resultSet.getMetaData();
    28. int columnCount = metaData.getColumnCount();
    29. // 每次遍历行值
    30. while (resultSet.next()) {
    31. T obj = (T) clazz.newInstance();
    32. for (int i = 1; i <= columnCount; i++) {
    33. Object value = resultSet.getObject(i);
    34. String columnName = metaData.getColumnName(i);
    35. String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
    36. Method method;
    37. if (value instanceof Timestamp) {
    38. method = clazz.getMethod(setMethod, Date.class);
    39. } else {
    40. method = clazz.getMethod(setMethod, value.getClass());
    41. }
    42. method.invoke(obj, value);
    43. }
    44. list.add(obj);
    45. }
    46. } catch (Exception e) {
    47. e.printStackTrace();
    48. }
    49. return list;
    50. }
    51. @Override
    52. public void close() {
    53. if (null == connection) return;
    54. try {
    55. connection.close();
    56. } catch (SQLException e) {
    57. e.printStackTrace();
    58. }
    59. }
    60. private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map parameterMap) throws SQLException, IllegalAccessException {
    61. int size = parameterMap.size();
    62. // 单个参数
    63. if (parameter instanceof Long) {
    64. for (int i = 1; i <= size; i++) {
    65. preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
    66. }
    67. return;
    68. }else{
    69. // TODO 后面紧跟的章节继续补充其他类型的入参
    70. }
    71. }
    72. }

    3.定义SqlSessionFactory接口

            每次执行一个SQL语句,应用程序都需要获取一个SqlSession对象。SqlSession对象是执行持久化操作的入口点,可以用于执行SQL语句、刷新缓存、提交事务等操作。建议在使用完SqlSession后,及时关闭它来释放资源。

    1. public interface SqlSessionFactory {
    2. SqlSession openSession();
    3. }

    4.DefaultSqlSessionFactory(上述接口实现类)

            构造方法中向下传递了Configuration配置文件

    1. public class DefaultSqlSessionFactory implements SqlSessionFactory {
    2. private final Configuration configuration;
    3. public DefaultSqlSessionFactory(Configuration configuration) {
    4. this.configuration = configuration;
    5. }
    6. @Override
    7. public SqlSession openSession() {
    8. return new DefaultSqlSession(configuration.getConnection(), configuration.getMapperElement());
    9. }
    10. }

    5.SqlSessionFactoryBuilder

            数据库操作的核心类,负责解析Mapper文件(拿datasource,数据库连接信息,mapper文件中sql的各个信息如id,入返参类型,sql)

    1. public class SqlSessionFactoryBuilder {
    2. public DefaultSqlSessionFactory build(Reader reader) {
    3. SAXReader saxReader = new SAXReader();
    4. Document document = null;
    5. try {
    6. document = saxReader.read(new InputSource(reader));
    7. // 拿到根标签元素
    8. Element rootElement = document.getRootElement();
    9. Configuration configuration = parseConfiguration(rootElement);
    10. return new DefaultSqlSessionFactory(configuration);
    11. } catch (DocumentException e) {
    12. e.printStackTrace();
    13. }
    14. return null;
    15. }
    16. public Configuration parseConfiguration(Element rootElement) {
    17. Configuration configuration = new Configuration();
    18. configuration.setDataSource(dataSource(rootElement.selectNodes("//dataSource")));
    19. configuration.setConnection(connection(configuration.getDataSource()));
    20. configuration.setMapperElement(mapperElement(rootElement.selectNodes("//mappers")));
    21. return configuration;
    22. }
    23. private Map dataSource(List list) {
    24. Map dataSource = new HashMap<>(4);
    25. Element element = list.get(0);
    26. List content = element.content();
    27. for (Object o : content) {
    28. Element e = (Element) o;
    29. String name = e.attributeValue("name");
    30. String value = e.attributeValue("value");
    31. dataSource.put(name, value);
    32. }
    33. return dataSource;
    34. }
    35. private Connection connection(Map dataSource) {
    36. try {
    37. return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
    38. } catch (SQLException e) {
    39. e.printStackTrace();
    40. }
    41. return null;
    42. }
    43. private Map mapperElement(List list) {
    44. Map map = new HashMap<>();
    45. Element element = list.get(0);
    46. List content = element.content();
    47. try {
    48. for (Object o : content) {
    49. Element e = (Element) o;
    50. // 拿到mapper文件对应地址
    51. String resource = e.attributeValue("resource");
    52. Reader reader = Resources.getResourceAsReader(resource);
    53. SAXReader saxReader = new SAXReader();
    54. Document document = saxReader.read(new InputSource(reader));
    55. Element rootElement = document.getRootElement();
    56. String namespace = rootElement.attributeValue("namespace");
    57. List selectNodes = rootElement.selectNodes("select");
    58. for (Element ele : selectNodes) {
    59. String id = ele.attributeValue("id");
    60. String parameterType = ele.attributeValue("parameterType");
    61. String resultType = ele.attributeValue("resultType");
    62. String sql = ele.getText();
    63. // ? 匹配
    64. Map parameter = new HashMap<>();
    65. Pattern pattern = Pattern.compile("(#\\{(.*?)})");
    66. Matcher matcher = pattern.matcher(sql);
    67. for (int i = 1; matcher.find(); i++) {
    68. String g1 = matcher.group(1);
    69. String g2 = matcher.group(2);
    70. parameter.put(i, g2);
    71. sql = sql.replace(g1, "?");
    72. }
    73. XNode xNode = new XNode();
    74. xNode.setId(id);
    75. xNode.setNameSpace(namespace);
    76. xNode.setParameterType(parameterType);
    77. xNode.setResultType(resultType);
    78. xNode.setSql(sql);
    79. xNode.setParameter(parameter);
    80. map.put(namespace + "." + id, xNode);
    81. }
    82. }
    83. }catch (Exception e){
    84. e.printStackTrace();
    85. }
    86. return map;
    87. }
    88. }

    测试验证

    建表

    1. DROP TABLE IF EXISTS `user`;
    2. CREATE TABLE `user` (
    3. `id` bigint(20) NOT NULL COMMENT '自增id',
    4. `userId` varchar(9) DEFAULT NULL COMMENT '用户ID',
    5. `userNickName` varchar(32) DEFAULT NULL COMMENT '用户昵称',
    6. `userHead` varchar(255) DEFAULT NULL COMMENT '用户头像',
    7. `userPassword` varchar(255) DEFAULT NULL COMMENT '用户密码',
    8. `createTime` datetime DEFAULT NULL COMMENT '创建时间',
    9. `updateTime` datetime NOT NULL COMMENT '更新时间',
    10. PRIMARY KEY (`id`)
    11. ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
    12. -- ----------------------------
    13. -- Records of user
    14. -- ----------------------------
    15. BEGIN;
    16. INSERT INTO `user` VALUES (1, '001', 'xxx', '001', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
    17. INSERT INTO `user` VALUES (2, '002', 'xxx2', '002', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
    18. COMMIT;
    19. SET FOREIGN_KEY_CHECKS = 1;

    定义POJO及DAO

    1. @Data
    2. public class User {
    3. private Long id;
    4. private String userId; // 用户ID
    5. private String userNickName; // 昵称
    6. private String userHead; // 头像
    7. private String userPassword; // 密码
    8. private Date createTime; // 创建时间
    9. private Date updateTime; // 更新时间
    10. }
    1. public interface IUserDao {
    2. User queryUserInfoById(Long id);
    3. }

    ORM配置文件--mybatis-config-datasource.xml

    1. "1.0" encoding="UTF-8"?>
    2. "-//mybatis.org//DTD Config 3.0//EN"
    3. "http://mybatis.org/dtd/mybatis-3-config.dtd">
    4. default="development">
    5. "development">
    6. "JDBC"/>
    7. "POOLED">
    8. "driver" value="com.mysql.jdbc.Driver"/>
    9. "url" value="jdbc:mysql://172.17.1.245:3306/airticketbasedb?useUnicode=true"/>
    10. "username" value="write"/>
    11. "password" value="write123"/>
    12. "mapper/User_Mapper.xml"/>

    Mapper配置

    UserMapper.xml

    1. "1.0" encoding="UTF-8"?>
    2. "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    3. "com.example.minimybatis.dao.IUserDao">

     测试类

    1. public class ApiTest {
    2. @Test
    3. public void test(){
    4. String resouce = "mybatis-config-datasource.xml";
    5. Reader reader;
    6. try{
    7. reader = Resources.getResourceAsReader(resouce);
    8. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    9. SqlSession sqlSession = sqlSessionFactory.openSession();
    10. User user = sqlSession.selectOne(
    11. "com.example.minimybatis.dao.IUserDao.queryUserInfoById",
    12. 1L);
    13. System.out.println(JSONObject.toJSONString(user));
    14. }catch (Exception e){
    15. e.printStackTrace();
    16. }
    17. }
    18. }

     总结

    比mybatis小很多,取其(mybaits)精华来达到掌握ORM框架的目的

  • 相关阅读:
    面试美团被问到了Redis,搞懂这几个问题,让你轻松吊打面试官
    LeetCode Hot100之八:3.无重复字符的最长子串
    eBPF-3-profile的源码解析
    python yield含义
    【通信】基于MVDR LCEC GSC PCI MWF EC PCA_MVB多种算法实现自适应波束生成
    Hadoop高可用(Hadoop2.x)
    2022年Java行业分析报告,全面解析别错过
    RFID技术在仓储物流供应链管理中的应用
    ASP.Net MVC--起步
    小白学编程(CSS):呼吸灯
  • 原文地址:https://blog.csdn.net/weixin_44758548/article/details/131754571