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框架的目的

相关推荐
弗拉唐几秒前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
2401_857610031 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_1 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞1 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货1 小时前
Rust 的简介
开发语言·后端·rust
monkey_meng2 小时前
【遵守孤儿规则的External trait pattern】
开发语言·后端·rust
天天进步20152 小时前
Vue+Springboot用Websocket实现协同编辑
vue.js·spring boot·websocket
Estar.Lee2 小时前
时间操作[计算时间差]免费API接口教程
android·网络·后端·网络协议·tcp/ip
乌啼霜满天2493 小时前
Spring 与 Spring MVC 与 Spring Boot三者之间的区别与联系
java·spring boot·spring·mvc