实现简单的MyBatis框架

一、传统JDBC存在的问题与解决思路

首先我们要知道为什么我们需要引入向MyBatis这样的ORM框架(传统JDBC访问存在的问题),以及怎样才能解决传统所发现的问题(实现简单的ORM框架)

传统JDBC存在的问题

简单的JDBC操作
上图是一个简单的原生JDBC操作,我们不难发现存在以下问题:

  1. 数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能
  2. sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,sql变动需要改变java代码。
  3. 使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可多可少,修改sql还要修改代码,系统不易维护。
  4. 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析⽐较⽅便

解决思路

  1. 频繁创建/释放资源,可以考虑使用如线程池相似的概念(数据库连接池)
  2. sql语句不硬编码在java代码中,实现sql与java业务代码解耦,可放在其它文件(xml,或其它java文件),且可动态添加参数(使用集合或pojo传递入参),并动态构建出sql
  3. 使⽤反射、内省等底层技术,⾃动将实体与表进⾏属性与字段的⾃动映射(可动态设置返回值)

二、实现简单的mybatis框架

流程图

大致流程

框架整体结构

项目结构

  1. config包为常用设置类
  2. enums包为常用枚举
  3. io包为读取文件(配置文件/映射文件)相关类
  4. pojo包为封装中间态/结果集
  5. sqlsession包中有sqlsession工厂类,对应工厂实例与执行器executor方法
  6. utils为常用工具类

三、详细流程讲解

调用方传递配置文件与映射文件路径

// 传递配置文件
InputStream inputStream = Resource.getResourceAsSteam("SqlMapConfig.xml");

SqlMapConfig.xml

<configuration>
    <!-- 数据源 -->
    <dataSource>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver" ></property>
        <property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/blog?characterEncoding=utf-8&amp;serverTimezone=UTC" ></property>
        <property name="username" value="root" ></property>
        <property name="password" value="***" ></property>
    </dataSource>

    <!-- 加载配置文件无需加载两次 所以这里还需存放mapper.xml 的全路径 -->
    <mapper resource = "UserMapper.xml"></mapper>
</configuration>

UserMapper.xml

<mapper namespace = "dao.SysUserDAO">
    <select parameterType = "entity.SysUserEntity" id="queryById" resultType = "entity.SysUserEntity">
        SELECT * FROM sys_user WHERE id = #{id}
    </select>

    <select id="queryAll" resultType = "entity.SysUserEntity">
        SELECT * FROM sys_user
    </select>

    <select id="countAll" resultType = "java.lang.Integer">
        SELECT count(1) FROM sys_user
    </select>

    <insert parameterType = "entity.SysUserEntity" id="insertUser">
        INSERT INTO sys_user VALUES (#{id}, #{loginName}, #{password}, #{email}, #{phone}, #{headPhoto}, #{loginIp}, #{loginDate}, #{loginFlag}, #{motto}, #{createBy}, #{createDate}, #{updateBy}, #{updateDate}, #{remarks}, #{delFlag})
    </insert>

    <update parameterType = "entity.SysUserEntity" id="updateNameById">
        UPDATE sys_user SET login_name = #{loginName} WHERE id = #{id}
    </update>

    <delete parameterType = "entity.SysUserEntity" id="deleteById">
        DELETE FROM sys_user WHERE id = #{id}
    </delete>
</mapper>

解析配置文件

调用方调用SqlSessionFactoryBuilder 的build方法将输入流传入

// 传递配置文件
InputStream inputStream = Resource.getResourceAsSteam("SqlMapConfig.xml");
// 创建sqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

框架方的SqlSessionFactoryBuilder build 方法

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException {
        // 1.使用dom4j解析配置文件,将解析结果封装至Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);

        // 2.创建sqlSessionFactory 工厂类:生产sqlsession会话对象(与数据库家交互的对象)工厂模式
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    }
}

com.gray.config.XMLConfigBuilder#parseConfig(解析配置文件与mapper文件并封装为Configuration对象)

public class XMLConfigBuilder {

    private Configuration configuration;

    public XMLConfigBuilder() {
        this.configuration = new Configuration();
    }

    /**
     * 使用dom4j将配置文件解析封装Configuration
     *
     * @param inputStream
     * @return
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();

        // 基础配置信息读取/封装
        // 读取所有property 节点
        List<Element> propertyNodes = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : propertyNodes) {
            String name = element.attributeValue(ElementAttributeValue.NAME.getAttributeValue());
            String value = element.attributeValue(ElementAttributeValue.VALUE.getAttributeValue());
            properties.setProperty(name, value);
        }
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty(PropertiesProperty.DRIVER_CLASS.getProperty()));
        comboPooledDataSource.setJdbcUrl(properties.getProperty(PropertiesProperty.JDBC_URL.getProperty()));
        comboPooledDataSource.setUser(properties.getProperty(PropertiesProperty.USER_NAME.getProperty()));
        comboPooledDataSource.setPassword(properties.getProperty(PropertiesProperty.PASSWORD.getProperty()));
        configuration.setDataSource(comboPooledDataSource);

        // mapper文件读取
        // 读取所有mapper节点
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue(ElementAttributeValue.RESOURCE.getAttributeValue());
            // 获取所有mapper配置文件路径并解析
            InputStream resourceAsSteam = Resource.getResourceAsSteam(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsSteam);
        }
        return configuration;
    }

}

解析mapper文件

这里的statementId 十分重要,主要是为了确定唯一一条sql执行计划,通常使用mapper上的namespace + sql上的id确定

       String id = element.attributeValue(ElementAttributeValue.ID.getAttributeValue());
        String parameterType = element.attributeValue(ElementAttributeValue.PARAMETER_TYPE.getAttributeValue());
        String resultType = element.attributeValue(ElementAttributeValue.RESULT_TYPE.getAttributeValue());
        // 获取sql
        String elementName = element.getName();
        String sqlText = element.getTextTrim();
        DMLOperating dmlOperating = DMLOperating.sqlToOperating(sqlText);
        if (StringUtils.isBlank(sqlText) || dmlOperating == null || !elementName.toLowerCase().equals(dmlOperating.getOperating())) {
            throw new RuntimeException("标签类型与sql类型不一致");
        }
        MappedStatement mappedStatement = new MappedStatement();
        mappedStatement.setId(id);
        // 入参类型
        mappedStatement.setParameterType(parameterType);
        // 返回值类型
        mappedStatement.setResultType(resultType);
        // 所写入sql
        mappedStatement.setSql(sqlText);
        // dml 类型 select, insert等
        mappedStatement.setDmlOperating(dmlOperating);
        StringJoiner statementId = new StringJoiner(".");
        // statementId id 为 namespace与sql id 相结合唯一确定一个sql执行语句
        statementId.add(namespace).add(id);
        configuration.getMappedStatementMap().put(statementId.toString(), mappedStatement);

封装完成的Configuration 对象

@Data
public class Configuration implements Serializable {

    private DataSource dataSource;

    /**
     * 加载的映射对象,这样向下传递参数时就只用传递Configuration对象
     * key:Statement namespace + id
     * value:封装的MappedStatement
     */
    private Map<String, MappedStatement> mappedStatementMap = Maps.newHashMap();

}

Configuration 对象中的MappedStatement

@Data
public class MappedStatement implements Serializable {

    // id标识
    private String id;

    // 返回值类型
    private String resultType;

    // 参数值
    private String parameterType;

    // sql语句
    private String sql;

    // dml 类型
    private DMLOperating dmlOperating;

}

最后生成将生成的configuration 放入工厂实例中返回给调用端(第二步)

public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException {
        // 1.使用dom4j解析配置文件,将解析结果封装至Configuration
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);

        // 2.创建sqlSessionFactory 工厂类:生产sqlsession会话对象(与数据库家交互的对象)工厂模式
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    }
}

寻找对应sql计划

调用端获取到工厂后通过工厂生成sqlsession(sqlsession用于sql会话)

 // 创建sqlSession
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlsession = sqlSessionFactory.openSession();

openSession 其实就只是创建了一个sqlsession

@AllArgsConstructor
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration configuration;

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

通过getmapper形式获取对应sql实例
先看看sqlsession中内容

public interface SqlSession {

    <E> List<E> selectList(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException;

    <E> E selectOne(String statementId, Object... params) throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException;

    void insert(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException;

    void update(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException;

    void delete(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException;

    <E> E getMapper(Class<?> mapperClass);

}

再来看看它的实现类DefaultSqlSession

@AllArgsConstructor
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        List<Object> resultList = simpleExecutor.query(configuration, configuration.getMappedStatementMap().get(statementId), params);
        return (List<E>) resultList;
    }

    @Override
    public <E> E selectOne(String statementId, Object... params) throws ClassNotFoundException, SQLException, InstantiationException, IllegalAccessException, NoSuchFieldException {
        List<Object> objects = selectList(statementId, params);
        if (objects.size() == 1) {
            return (E) objects.get(0);
        } else {
            throw new RuntimeException("查询结果为空/有多条");
        }
    }

    @Override
    public void insert(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        simpleExecutor.query(configuration, configuration.getMappedStatementMap().get(statementId), params);
    }

    @Override
    public void update(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        simpleExecutor.query(configuration, configuration.getMappedStatementMap().get(statementId), params);
    }

    @Override
    public void delete(String statementId, Object... params) throws ClassNotFoundException, SQLException, NoSuchFieldException, InstantiationException, IllegalAccessException {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        simpleExecutor.query(configuration, configuration.getMappedStatementMap().get(statementId), params);
    }

    @Override
    public <E> E getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理为DAO接口生成代理对象
        Object instance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;
                MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                if (mappedStatement == null) {
                    return null;
                }
                // 暂时只处理简单dml
                DMLOperating dmlOperating = mappedStatement.getDmlOperating();
                if (dmlOperating == null) {
                    return null;
                }
                switch (dmlOperating) {
                    case SELECT: {
                        Type genericExceptionTypes = method.getGenericReturnType();
                        if (genericExceptionTypes instanceof ParameterizedType) {
                            return selectList(statementId, args);
                        }
                        return selectOne(statementId, args);
                    }
                    case UPDATE: {
                        update(statementId, args);
                    }
                    case DELETE: {
                        delete(statementId, args);
                    }
                    case INSERT: {
                        insert(statementId, args);
                    }
                }
                return null;
            }
        });
        return (E) instance;
    }
}

这里主要要说下getmapper方法,调用端使用getmapper方法将本身的类(mapper接口)当做入参放入,之后mapper这里使用动态代理,当有当前对象执行对象方法时都会先执行getmapper中的invoke方法,这样就能实现动态根据当前方法的classname拼装出statementId 找出对应mappedStatement 来执行对应sql

执行sql

不管是执行到SqlSession 哪个方法最后都会执行到executor的query方法,在这里就会连接JDBC最后拼装sql执行sql

public class SimpleExecutor implements Executor {

    private static Pattern linePattern = Pattern.compile("_(\\w)");

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... object) throws SQLException, ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
        // 1.注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2.获取sql 转换#{} 转换为 ?(?才能被jdbc识别)
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3. 获取预处理对象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4.设置参数
        // 获取参数对象属性值
        if (StringUtils.isNotBlank(mappedStatement.getParameterType())) {
            Class<?> parameterTypeClass = Class.forName(mappedStatement.getParameterType());
            List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
            for (int i = 0; i < parameterMappingList.size(); i++) {
                ParameterMapping parameterMapping = parameterMappingList.get(i);
                String content = parameterMapping.getContent();
                // 使用反射获取实体对象的属性值
                Field declaredField = parameterTypeClass.getDeclaredField(content);
                declaredField.setAccessible(true);
                Object o = declaredField.get(object[0]);
                preparedStatement.setObject(i + 1, o);
            }
        }

        // 5.执行sql
        ResultSet resultSet = null;
        if (DMLOperating.executeQuery(mappedStatement.getDmlOperating())) {
            resultSet = preparedStatement.executeQuery();
        } else {
            preparedStatement.execute();
        }

        ArrayList<Object> objects = new ArrayList<>();
        // 6.封装返回结果
        // 获取返回值
        if (StringUtils.isNotBlank(mappedStatement.getResultType()) && resultSet != null) {
            Class<?> resultTypeClass = Class.forName(mappedStatement.getResultType());
            boolean isBaseType = isBaseType(resultTypeClass, false);
            while (resultSet.next()) {
                // 生成结果对象
                Object resultObject = null;
                if (!isBaseType) {
                    resultObject = resultTypeClass.newInstance();
                }
                // 取出原始数据(其中有字段名)
                ResultSetMetaData metaData = resultSet.getMetaData();
                for (int i = 1; i <= metaData.getColumnCount(); i++) {
                    // 字段名(i要从1开始)
                    String columnName = metaData.getColumnName(i);
                    // 获取字段值
                    Object columnValue = resultSet.getObject(columnName);
                    if (isBaseType) {
                        resultObject = handleBaseTypeObject(resultTypeClass, columnValue);
                    } else {
                        try {
                            // 使用反射/内省完成结果封装(转换驼峰)
                            PropertyDescriptor propertyDescriptor = new PropertyDescriptor(lineToHump(columnName), resultTypeClass);
                            Method writeMethod = propertyDescriptor.getWriteMethod();
                            // 将值设置带结果对象中
                            writeMethod.invoke(resultObject, columnValue);
                        } catch (Exception e) {
                            System.out.println(e);
                        }
                    }
                }
                objects.add(resultObject);
            }
        }
        return (List<E>) objects;
    }


    /**
     * 完成对#{} 解析
     * 1.#{} -> ?
     * 2.解析#{}中的值
     *
     * @param sql
     * @return
     */
    public BoundSql getBoundSql(String sql) {
        // 标记处理类:配置标记解析器完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        // 解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        // #{} 解析出来的参数名称(属性名)
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        return new BoundSql(parseSql, parameterMappings);
    }

    /**
     * 下划线转驼峰
     *
     * @param str
     * @return
     */
    public static String lineToHump(String str) {
        str = str.toLowerCase();
        Matcher matcher = linePattern.matcher(str);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }


    /**
     * 判断对象属性是否是基本数据类型,包括是否包括string
     *
     * @param className
     * @param incString 是否包括string判断,如果为true就认为string也是基本数据类型
     * @return
     */
    public boolean isBaseType(Class className, boolean incString) {
        if (incString && className.equals(String.class)) {
            return true;
        }
        return className.equals(Integer.class) ||
                className.equals(int.class) ||
                className.equals(Byte.class) ||
                className.equals(byte.class) ||
                className.equals(Long.class) ||
                className.equals(long.class) ||
                className.equals(Double.class) ||
                className.equals(double.class) ||
                className.equals(Float.class) ||
                className.equals(float.class) ||
                className.equals(Character.class) ||
                className.equals(char.class) ||
                className.equals(Short.class) ||
                className.equals(short.class) ||
                className.equals(Boolean.class) ||
                className.equals(boolean.class);
    }

    public Object handleBaseTypeObject(Class<?> resultTypeClass, Object columnValue) {
        if ((resultTypeClass.equals(Integer.class) || resultTypeClass.equals(int.class)) && columnValue != null) {
            return Integer.valueOf(columnValue.toString());
        }
        return columnValue;
    }

}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值