美文网首页
框架学习笔记:自定义持久层框架的简单实现

框架学习笔记:自定义持久层框架的简单实现

作者: alex很累 | 来源:发表于2023-03-12 23:10 被阅读0次

一、分析JDBC操作问题

 public static void main(String[] args) {
        //数据库连接对象
        Connection connection = null;
        //预处理对象
        PreparedStatement preparedStatement = null;
        //结果集对象
        ResultSet resultSet = null;

        try {
            //1.加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2.通过驱动 获取数据库连接对象
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
            //3.构造查询SQL
            String sql = "select * from user where userName=?";
            //4.通过连接获取预处理对象
            preparedStatement = connection.prepareStatement(sql);
            //5.设置参数
            preparedStatement.setString(1, "lcg");
            //6.查询数据库
            resultSet = preparedStatement.executeQuery();
            //7.遍历查询结果,封装成对象
            while (resultSet.next()) {
                String id = resultSet.getString("id");
                String userName = resultSet.getString("userName");
                System.out.println("id:" + id + ",userName=" + userName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭resultSet
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //关闭preparedStatement
            if (preparedStatement != null) {
                try {
                    preparedStatement.cancel();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //关闭connection
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

    }

1.1 问题分析
  1. 数据库配置信息存在硬编码
    解决:将参数写到外部配置文件,然后通过读取配置文件来获取连接参数。
  2. 频繁创建、释放数据库连接,造成系统资源的浪费,从而影响系统性能。
    解决:连接池
  3. sql语句preparedStatement传参存在硬编码
    解决:将sql写到外部配置文件,程序加载配置文件,获取sql。
  4. 对结果集解析存在硬编码,sql变化会导致解析结果的代码发生变化
    解决:利用JAVA的反射特性实现sql结果的解析。
1.2 问题解决思路总结
  • 使用数据库连接池 统一管理 数据库连接;
  • 将数据库连接信息、sql语句等抽取到xml配置文件中;
  • 使用反射、内省等底层技术,自动将实体与表进行属性和字段的自动映射。

二、自定义框架设计

2.1 设计思路

使用端(项目)
需要提供核心配置文件,其中包括数据库配置信息以及sql配置信息;此外,还需引入我们后面写的自定义框架的jar包;另外,还需要测试使用的实体类以及dao类。
配置文件:

  • sqlMapConfig.xml:存放数据库连接信息,并且需要存放mapper.xml的所有路径。
  • mapper.xml:存放sql配置信息。

框架端

  1. 读取配置文件(使用端会将配置文件以流的形式传递过来,然后转换成对象)
    A. Configuration类:存放数据库基本信息,并以Map的形式(namespace.id作为key)存储sql语句;
    B. MappedStatement类:sql的基本信息,包括其id、输入类型、输出类型、sql语句。
  2. 解析配置文件
    创建sqlSessionFactoryBuilder类:
    具体方法:public SqlSessionFactory build(InputStream in)
    A. 使用dom4j解析配置文件,将解析出来的内容封装到Configuration中;
    B. 创建sqlSessionFactory对象:生产sqlSession会话对象(工厂模式)
  3. 创建SqlSessionFactory接口及实现类:
    ⽅法:openSession() : 获取sqlSession接⼝的实现类实例对象
  4. 创建sqlSession接⼝及实现类DefaultSqlSession:主要封装crud⽅法
    ⽅法:
    selectList(String statementId,Object param):查询所有
    selectOne(String statementId,Object param):查询单个
  5. 创建Executor接口及其实现类SimpleExecutor实现类
    sqlSessionJDBC代码封装在这里。
  6. 使用JDK动态代理来为Dao接口生成代理对象,通过代理对象调用方法都会执行invoke方法,从而去调用DefaultSqlSession的具体方法。(代理模式)
2.2 自定义持久层框架实现
2.2.1 使用端(项目)

sqlMapConfig.xml

<configuration>
    <!-- 数据库连接信息 -->
    <property name="driver" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/simple_db"></property>
    <property name="username" value="root"></property>
    <property name="password" value="admin123"></property>

    <!-- 引入所有的sql配置文件 -->
    <mappers>
        <mapper resource="sysUserMapper.xml"></mapper>
    </mappers>
</configuration>

sysUserMapper.xml

<mapper namespace="com.alex.dao.SysUserDao">
    <select id="selectAll" resultType="com.alex.entity.SysUser">
        select *
        from sys_user
    </select>

    <select id="selectOne" parameterType="com.alex.entity.SysUser" resultType="com.alex.entity.SysUser">
        select *
        from sys_user
        where id = #{id} and name = #{name}
    </select>
</mapper>

SysUser.java

package com.alex.entity;

public class SysUser {
    private String id;

    private String name;

    private Integer age;

    public SysUser() {
    }

    public SysUser(String id, String name, Integer age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "SysUser{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

SysUserDao.java

package com.alex.dao;

import com.alex.entity.SysUser;

import java.util.List;

public interface SysUserDao {
    List<SysUser> selectAll();

    SysUser selectOne(SysUser sysUser);
}
2.2.2 框架端

Resources.java

package com.alex.io;

import java.io.InputStream;

public class Resources {
    public static InputStream getResourceAsStream(String path) {
        //从classpath的根路径获取配置文件
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }
}

Configuration.java

package com.alex.entity;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

public class Configuration {
    /**
     * 数据源
     */
    private DataSource dataSource;

    /**
     * 所有的sql信息
     */
    private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }
}

MappedStatement.java

package com.alex.entity;

public class MappedStatement {
    private String id;
    private String parameterType;
    private String resultType;
    private String sqlText;

    public MappedStatement() {
    }

    public MappedStatement(String id, String parameterType, String resultType, String sqlText) {
        this.id = id;
        this.parameterType = parameterType;
        this.resultType = resultType;
        this.sqlText = sqlText;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getParameterType() {
        return parameterType;
    }

    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSqlText() {
        return sqlText;
    }

    public void setSqlText(String sqlText) {
        this.sqlText = sqlText;
    }
}

SqlSessionFactoryBuilder.java

package com.alex.sqlSession;

import com.alex.config.SqlMapConfigBuilder;
import com.alex.entity.Configuration;
import org.dom4j.DocumentException;

import java.beans.PropertyVetoException;
import java.io.InputStream;

public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws PropertyVetoException, DocumentException {
        // 第一:使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        SqlMapConfigBuilder sqlMapConfigBuilder = new SqlMapConfigBuilder();
        Configuration configuration = sqlMapConfigBuilder.config(in);

        // 第二:创建sqlSessionFactory对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;
    }
}

DefaultSqlSession.java

package com.alex.sqlSession;

import com.alex.entity.Configuration;
import com.alex.entity.MappedStatement;

import java.lang.reflect.*;
import java.util.List;

public class DefaultSqlSession implements SqlSession {
    private Configuration configuration;

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

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        SimpleExecutor executor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return executor.query(configuration, mappedStatement, params);
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> list = this.selectList(statementId, params);
        if (list.size() == 0) {
            return null;
        }
        if (list.size() == 1) {
            return (T) list.get(0);
        }
        throw new RuntimeException("返回结果过多.");
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK动态代理来为Dao接口生成代理对象;通过代理对象调用方法都会执行invoke方法
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 1.准备参数 statementId
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;
                Type genericReturnType = method.getGenericReturnType();
                // 2.调用方法
                // ParameterizedType 表示参数化类型,例如 Collection<String>。
                if (genericReturnType instanceof ParameterizedType) {
                    return selectList(statementId, args);
                }
                return selectOne(statementId, args);
            }
        });
        return (T) proxyInstance;
    }
}

SimpleExecutor.java

package com.alex.sqlSession;

import com.alex.config.BoundSql;
import com.alex.entity.Configuration;
import com.alex.entity.MappedStatement;
import com.alex.utils.GenericTokenParser;
import com.alex.utils.ParameterMapping;
import com.alex.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1.获取连接
        Connection connection = configuration.getDataSource().getConnection();
        // 2.转换sql语句 #{}转占位符?,并获取参数列表
        String sql = mappedStatement.getSqlText();
        BoundSql boundSql = getBoundSql(sql);
        // 3.获取预处理statement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 4.设置参数
        String parameterTypeName = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterTypeName);
        for (int i = 0; i < boundSql.getParameterMappingList().size(); i++) {
            ParameterMapping parameterMapping = boundSql.getParameterMappingList().get(i);
            // 利用反射特性,寻找value
            Field field = parameterTypeClass.getDeclaredField(parameterMapping.getContent());
            // 暴力访问
            field.setAccessible(true);
            Object o = field.get(params[0]);

            preparedStatement.setObject(i + 1, o);
        }
        // 5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        // 6.封装结果集
        List<Object> list = new ArrayList<>();
        Class<?> resultTypeClass = getClassType(mappedStatement.getResultType());
        while (resultSet.next()) {
            Object item = resultTypeClass.newInstance();
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                // 获取参数名称
                String columnName = metaData.getColumnName(i);
                // 获取参数value,这里只用varchar和int举例
                int columnType = metaData.getColumnType(i);
                Object value = null;
                if (columnType == Types.INTEGER) {
                    value = resultSet.getInt(columnName);
                } else if (columnType == Types.VARCHAR) {
                    value = resultSet.getString(columnName);
                }
                // 利用反射以及内省(属性描述器)完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method write = propertyDescriptor.getWriteMethod();
                write.invoke(item, value);
            }
            list.add(item);
        }

        return (List<E>) list;
    }

    private Class<?> getClassType(String parameterTypeName) throws ClassNotFoundException {
        if (parameterTypeName != null && parameterTypeName.length() > 0) {
            return Class.forName(parameterTypeName);
        }
        return null;
    }

    private BoundSql getBoundSql(String sql) {
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
        // sql
        String parseSql = parser.parse(sql);
        // 参数
        List<ParameterMapping> parameterMappingList = tokenHandler.getParameterMappings();
        return new BoundSql(parseSql, parameterMappingList);
    }

}

其他代码略,可以到这里阅读。

2.2.3 测试
package test;

import com.alex.dao.SysUserDao;
import com.alex.entity.SysUser;
import com.alex.io.Resources;
import com.alex.sqlSession.SqlSession;
import com.alex.sqlSession.SqlSessionFactory;
import com.alex.sqlSession.SqlSessionFactoryBuilder;
import org.dom4j.DocumentException;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;

public class IPersistentTest {
    @Test
    public void test() throws PropertyVetoException, DocumentException {
        InputStream resourceAsSteam = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // test
        SysUserDao sysUserDao = sqlSession.getMapper(SysUserDao.class);

        System.out.println("Test: selectOne");
        SysUser user = new SysUser("1", "xx", null);
        SysUser find = sysUserDao.selectOne(user);
        System.out.println(find);

        System.out.println("");
        System.out.println("Test: selectAll");
        List<SysUser> list = sysUserDao.selectAll();
        for (SysUser item : list) {
            System.out.println(item);
        }
    }
}

运行结果

相关文章

  • 自定义持久层框架 -- 功能优化

    一、自定义持久层框架问题分析 Dao层在使用自定义持久层框架的时候,存在代码重复,整个操作的过程模板重复(加载配置...

  • 自定义持久层框架思路

    使用端:(项目):引入自定义持久层框架的jar包 自定义持久层框架本身:(工程):本质就是对JDBC代码进行了封装

  • 手写Mybatis

    JDBC问题分析 自定义持久层框架设计思路 使用者(项目):引入自定义持久层框架的jar包 提供两部分配置信息数据...

  • 持久层框架(一)自定义持久层框架

    文章内容来源:拉勾教育Java高薪训练营(侵权联系删除) 1 JDBC 存在的问题分析 JDBC 问题总结:原始...

  • MyBatis学习笔记

    最近学习MyBatis这个轻量型持久层框架,感觉入门很简单,但是深层次细节配置很多。本篇笔记从 配置文件->例子入...

  • Mybatis源码浅析(一)

    前言 最近项目中使用到了Mybatis持久层框架,由于从来没有深入的了解过基于Java语言实现的持久层框架,于是有...

  • SSH框架

    1.概述 SSH框架:Struts框架,基于MVC模式的应用层框架技术。Hibernate: 基于持久层的框架(数...

  • 自定义持久层框架

    源码地址[https://gitee.com/cwhenshuo/IPersistence] 前言:其实之前学习m...

  • 自定义持久层框架

    1.自定义持久层框架思路分析 JDBC问题分析:1.数据库配置信息存在硬编码问题。2.频繁创建释放数据库链接3.s...

  • Springboot 2.1.2 整合quartz 2.3.0

    说明: 核心框架: SpringBoot 2.1.2 任务调度框架: Quartz 2.3.0 持久层框架: my...

网友评论

      本文标题:框架学习笔记:自定义持久层框架的简单实现

      本文链接:https://www.haomeiwen.com/subject/ocqvldtx.html