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

一、分析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);
        }
    }
}

运行结果

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容