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);

java 复制代码
public interface SqlSession {

    <T> T selectOne(String statement, Object parameter);

    void close();
}

2.DefaultSqlSession(SqlSession的实现)

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

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

java 复制代码
public class DefaultSqlSession implements SqlSession{

    private Connection connection;

    private Map<String,XNode> mapperElement;

    public DefaultSqlSession(Connection connection, Map<String, XNode> mapperElement) {
        this.connection = connection;
        this.mapperElement = mapperElement;
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        XNode xNode = mapperElement.get(statement);
        Map<Integer, String> parameterMap = xNode.getParameter();
        try {
            PreparedStatement preparedStatement = connection.prepareStatement(xNode.getSql());
            buildParameter(preparedStatement, parameter, parameterMap);
            // SQL执行结果集的行数据
            ResultSet resultSet = preparedStatement.executeQuery();
            List<T> objects = resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));
            return objects.get(0);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }


    private <T> List<T> resultSet2Obj(ResultSet resultSet, Class<?> clazz) {
        List<T> list = new ArrayList<>();
        try {
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columnCount = metaData.getColumnCount();
            // 每次遍历行值
            while (resultSet.next()) {
                T obj = (T) clazz.newInstance();
                for (int i = 1; i <= columnCount; i++) {
                    Object value = resultSet.getObject(i);
                    String columnName = metaData.getColumnName(i);
                    String setMethod = "set" + columnName.substring(0, 1).toUpperCase() + columnName.substring(1);
                    Method method;
                    if (value instanceof Timestamp) {
                        method = clazz.getMethod(setMethod, Date.class);
                    } else {
                        method = clazz.getMethod(setMethod, value.getClass());
                    }
                    method.invoke(obj, value);
                }
                list.add(obj);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }
    

    @Override
    public void close() {
        if (null == connection) return;
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    private void buildParameter(PreparedStatement preparedStatement, Object parameter, Map<Integer, String> parameterMap) throws SQLException, IllegalAccessException {

        int size = parameterMap.size();
        // 单个参数
        if (parameter instanceof Long) {
            for (int i = 1; i <= size; i++) {
                preparedStatement.setLong(i, Long.parseLong(parameter.toString()));
            }
            return;
        }else{
            // TODO 后面紧跟的章节继续补充其他类型的入参
        }
        
    }
}

3.定义SqlSessionFactory接口

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

java 复制代码
public interface SqlSessionFactory {

    SqlSession openSession();
}

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

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

java 复制代码
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration.getConnection(), configuration.getMapperElement());
    }
}

5.SqlSessionFactoryBuilder

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

java 复制代码
public class SqlSessionFactoryBuilder {

    public DefaultSqlSessionFactory build(Reader reader) {
        SAXReader saxReader = new SAXReader();
        Document document = null;
        try {
            document = saxReader.read(new InputSource(reader));
            // 拿到根标签元素
            Element rootElement = document.getRootElement();
            Configuration configuration = parseConfiguration(rootElement);
            return new DefaultSqlSessionFactory(configuration);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        return null;
    }

    public Configuration parseConfiguration(Element rootElement) {
        Configuration configuration = new Configuration();
        configuration.setDataSource(dataSource(rootElement.selectNodes("//dataSource")));
        configuration.setConnection(connection(configuration.getDataSource()));
        configuration.setMapperElement(mapperElement(rootElement.selectNodes("//mappers")));
        return configuration;
    }

    private Map<String, String> dataSource(List<Element> list) {
        Map<String, String> dataSource = new HashMap<>(4);
        Element element = list.get(0);
        List content = element.content();
        for (Object o : content) {
            Element e = (Element) o;
            String name = e.attributeValue("name");
            String value = e.attributeValue("value");
            dataSource.put(name, value);
        }
        return dataSource;
    }

    private Connection connection(Map<String, String> dataSource) {
        try {
            return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    private Map<String, XNode> mapperElement(List<Element> list) {
        Map<String, XNode> map = new HashMap<>();
        Element element = list.get(0);
        List content = element.content();
        try {
            for (Object o : content) {
                Element e = (Element) o;
                // 拿到mapper文件对应地址
                String resource = e.attributeValue("resource");
                Reader reader = Resources.getResourceAsReader(resource);
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(new InputSource(reader));
                Element rootElement = document.getRootElement();
                String namespace = rootElement.attributeValue("namespace");
                List<Element> selectNodes = rootElement.selectNodes("select");
                for (Element ele : selectNodes) {
                    String id = ele.attributeValue("id");
                    String parameterType = ele.attributeValue("parameterType");
                    String resultType = ele.attributeValue("resultType");
                    String sql = ele.getText();
                    // ? 匹配
                    Map<Integer, String> parameter = new HashMap<>();
                    Pattern pattern = Pattern.compile("(#\\{(.*?)})");
                    Matcher matcher = pattern.matcher(sql);
                    for (int i = 1; matcher.find(); i++) {
                        String g1 = matcher.group(1);
                        String g2 = matcher.group(2);
                        parameter.put(i, g2);
                        sql = sql.replace(g1, "?");
                    }
                    XNode xNode = new XNode();
                    xNode.setId(id);
                    xNode.setNameSpace(namespace);
                    xNode.setParameterType(parameterType);
                    xNode.setResultType(resultType);
                    xNode.setSql(sql);
                    xNode.setParameter(parameter);
                    map.put(namespace + "." + id, xNode);
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return map;
    }
}

测试验证

建表

sql 复制代码
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` bigint(20) NOT NULL COMMENT '自增id',
  `userId` varchar(9) DEFAULT NULL COMMENT '用户ID',
  `userNickName` varchar(32) DEFAULT NULL COMMENT '用户昵称',
  `userHead` varchar(255) DEFAULT NULL COMMENT '用户头像',
  `userPassword` varchar(255) DEFAULT NULL COMMENT '用户密码',
  `createTime` datetime DEFAULT NULL COMMENT '创建时间',
  `updateTime` datetime NOT NULL COMMENT '更新时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
BEGIN;
INSERT INTO `user` VALUES (1, '001', 'xxx', '001', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
INSERT INTO `user` VALUES (2, '002', 'xxx2', '002', '123', '2023-07-14 17:33:55', '2023-07-14 17:33:58');
COMMIT;

SET FOREIGN_KEY_CHECKS = 1;

定义POJO及DAO

java 复制代码
@Data
public class User {

    private Long id;
    private String userId;          // 用户ID
    private String userNickName;    // 昵称
    private String userHead;        // 头像
    private String userPassword;    // 密码
    private Date createTime;        // 创建时间
    private Date updateTime;        // 更新时间
}
java 复制代码
public interface IUserDao {
     User queryUserInfoById(Long id);
}

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

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">

<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://172.17.1.245:3306/airticketbasedb?useUnicode=true"/>
                <property name="username" value="write"/>
                <property name="password" value="write123"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="mapper/User_Mapper.xml"/>
    </mappers>

</configuration>

Mapper配置

UserMapper.xml

java 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.minimybatis.dao.IUserDao">

    <select id="queryUserInfoById" parameterType="java.lang.Long" resultType="com.example.minimybatis.po.User">
        SELECT id, userId, userNickName, userHead, userPassword, createTime
        FROM user
        where id = #{id}
    </select>

</mapper>

测试类

java 复制代码
public class ApiTest {
    @Test
    public void test(){
        String resouce = "mybatis-config-datasource.xml";
        Reader reader;
        try{
            reader = Resources.getResourceAsReader(resouce);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            User user = sqlSession.selectOne(
                    "com.example.minimybatis.dao.IUserDao.queryUserInfoById",
                    1L);
            System.out.println(JSONObject.toJSONString(user));
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

总结

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

相关推荐
鬼火儿5 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
cr7xin5 小时前
缓存三大问题及解决方案
redis·后端·缓存
摇滚侠6 小时前
Spring Boot3零基础教程,Spring Boot 应用打包成 exe 可执行文件,笔记91 笔记92 笔记93
linux·spring boot·笔记
间彧6 小时前
Kubernetes的Pod与Docker Compose中的服务在概念上有何异同?
后端
间彧7 小时前
从开发到生产,如何将Docker Compose项目平滑迁移到Kubernetes?
后端
间彧7 小时前
如何结合CI/CD流水线自动选择正确的Docker Compose配置?
后端
间彧7 小时前
在多环境(开发、测试、生产)下,如何管理不同的Docker Compose配置?
后端
间彧7 小时前
如何为Docker Compose中的服务配置健康检查,确保服务真正可用?
后端
间彧7 小时前
Docker Compose和Kubernetes在编排服务时有哪些核心区别?
后端
间彧7 小时前
如何在实际项目中集成Arthas Tunnel Server实现Kubernetes集群的远程诊断?
后端