Java进阶之路-自定义持久层框架

Posted 程序员yqy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java进阶之路-自定义持久层框架相关的知识,希望对你有一定的参考价值。

1 持久层以及JDBC问题分析

1.1 什么是持久层

数据访问层又称为DAL或Dao层,有时候也称为是持久层,其功能主要是负责数据库的访问,简单的说法就是实现对数据表的Select(查询),Insert(插入),Update(更新),Delete(删除)等操作。

早期实现持久层使用JDBC操作即可完成数据层的操作,接触过JDBC的同学应该不陌生,那么既然JDBC已经能够完成数据库的操作,为什么还会出现各种持久层框架呢?有新框架出现说明JDBC本身还是存在一些问题。

1.2分析JDBC操作问题

JDBC代码示例

public static void main(String[] args)

    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try
    
        // 加载数据库驱动 
        Class.forName("com.mysql.jdbc.Driver");
        // 通过驱动管理类获取数据库链接
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis? characterEncoding=utf-8", "root", "root");
        // 定义sql语句?表示占位符
        String sql = "select * from user where username = ?";
        // 获取预处理
        statement preparedStatement = connection.prepareStatement(sql);
        // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
        preparedStatement.setString(1, "tom");
        // 向数据库发出sql执行查询,查询出结果集
        resultSet = preparedStatement.executeQuery();
        // 遍历查询结果集
        while(resultSet.next())
        
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            // 封装
            User user.setId(id);
            user.setUsername(username);
        
        System.out.println(user);
    
    catch(Exception e)
    
        e.printStackTrace();
    
    finally
    
        // 释放资源
        if(resultSet != null)
        
            try
            
                resultSet.close();
            
            catch(SQLException e)
            
                e.printStackTrace();
            
        
        if(preparedStatement != null)
        
            try
            
                preparedStatement.close();
            
            catch(SQLException e)
            
                e.printStackTrace();
            
        
        if(connection != null)
        
            try
            
                connection.close();
            
            catch(SQLException e)
            
                e.printStackTrace();
            
        
    

总结原始jdbc开发存在的问题如下:

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

1.3 问题解决思路

  1. 使用数据库连接池初始化连接资源
  2. 将sql语句、数据配置信息抽取到xml配置文件中
  3. 使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射

2 自定义持久层框架思路分析

有了以上1.3的问题解决思路,那接下来我们要自定义一个持久层框架,分析一下,使用者 真正关心的其实只有数据库配置信息和对应的sql语句。为了解决硬编码的问题,可以考虑让使用提供两个核心配置文件

使用端:

  1. sqlMapConfig.xml : 存放数据源信息,引入mapper.xml
  2. Mapper.xml : sql语句的配置文件信息

框架端:

  1. 读取配置文件
    读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储
    (1)Configuration : 存放数据库基本信息、Map<唯一标识,MappedStatement> ,这里Map存放所有sql语句对象的集合
    (2)MappedStatement:sql语句、statement类型、输入参数java类型、输出参数java类型

  2. 使用dom4j解析配置文件,将解析出来的内容封装到Configuration和MappedStatement中

  3. 创建一个使用类,定义增删查改的方法并加以实现,封装结果集。

3 自定义持久层框架实现

3.1 使用端

刚才我们说了使用只关心数据库信息,sql语句以及保存数据的实体

数据库配置文件

<configuration>
    <datasource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///zyd_mybatis"/>
        <property name="username" value="root"/>
        <property name="password" value="mimashi3124"/>
    </datasource>

	<!-- 存放mapper.xml全路径-->
    <mapper resource="UserMapper.xml"></mapper>
</configuration>

sql语句配置文件

<mapper namespace="com.yqy.dao.IUserDao">

    <!--    sql唯一标识 namespace.id组成:statementId-->
    <select id="findAll" resultType="com.yqy.pojo.User">
        select * from user
    </select>

    <select id="findByCondition" resultType="com.yqy.pojo.User" parameterType="com.yqy.pojo.User">
        select * from user where id = #id and username = #username
    </select>
</mapper>

定义实体

package com.yqy.pojo;

/**
 * Created by yangqy on 2020/11/2.
 */
public class User 

    private Integer id;

    private String username;

    public Integer getId() 
        return id;
    

    public void setId(Integer id) 
        this.id = id;
    

    public String getUsername() 
        return username;
    

    public void setUsername(String username) 
        this.username = username;
    

    @Override
    public String toString() 
        return "User" +
                "id=" + id +
                ", username='" + username + '\\'' +
                '';
    


3.2 框架端

框架端需要解析使用端的xml文件,以及数据库连接等,新建maven项目后引入如下依赖

<dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>

要解析数据,得先有保存数据的实体bean,则定义Configuration和MappedStatement两个实体,分别存放数据库配置信息、sql语句。

/**
 * Created by yangqy on 2020/11/2.
 */
public class Configuration 

    private DataSource dataSource;

    /**
     * key为statementId,value为封装好的mappedStatement对象
     */
    Map<String,MappedStatement> mappedStatementMap = new HashMap<>();

    public DataSource getDataSource() 
        return dataSource;
    

    public Configuration setDataSource(DataSource dataSource) 
        this.dataSource = dataSource;
        return this;
    

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

    public Configuration setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) 
        this.mappedStatementMap = mappedStatementMap;
        return this;
    


/**
 * Created by yangqy on 2020/11/2.
 */
public class MappedStatement 

    /**
     * id标识
     */
    private String id;
    /**
     * 返回值类型
     */
    private String resultType;
    /**
     * 参数值类型
     */
    private String parameterType;
    /**
     * 参数值类型
     */
    private String sql;


    public String getId() 
        return id;
    

    public MappedStatement setId(String id) 
        this.id = id;
        return this;
    

    public String getResultType() 
        return resultType;
    

    public MappedStatement setResultType(String resultType) 
        this.resultType = resultType;
        return this;
    

    public String getParameterType() 
        return parameterType;
    

    public MappedStatement setParameterType(String parameterType) 
        this.parameterType = parameterType;
        return this;
    

    public String getSql() 
        return sql;
    

    public MappedStatement setSql(String sql) 
        this.sql = sql;
        return this;
    


要读取文件还得以流的形式操作,定义Resources类

package com.yqy.io;

import java.io.InputStream;

/**
 * Created by yangqy on 2020/11/2.
 */
public class Resources 

    /**
     * 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
     *
     * @param path
     * @return
     */
    public static InputStream getResourceAsStream(String path) 
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    


定义xml解析类,进行解析操作,这里XmlMapperBuilder用来解析sql信息,XmlConfigBuilder解析配置信息

/**
 * Created by yangqy on 2020/11/3.
 */
public class XmlConfigBuilder 

    private Configuration configuration;

    public XmlConfigBuilder() 
        this.configuration = new Configuration();
    

    /**
     * 该方法就是使用dom4j将配置文件解析为Configuration对象
     *
     * @param inputStream
     * @return
     */
    public Configuration parseConfiguration(InputStream inputStream) throws DocumentException, PropertyVetoException 
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : list) 
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name, value);
        

        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass((properties.getProperty("driverClass")));
        comboPooledDataSource.setJdbcUrl((properties.getProperty("jdbcUrl")));
        comboPooledDataSource.setUser((properties.getProperty("username")));
        comboPooledDataSource.setPassword((properties.getProperty("password")));

        configuration.setDataSource(comboPooledDataSource);

        //mapper.xml解析:拿到路径-->字节输入流-->dom4进行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) 
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
        
        return configuration;
    


/**
 * Created by yangqy on 2020/11/3.
 */
public class XmlMapperBuilder 

    private Configuration configuration;

    public XmlMapperBuilder(Configuration configuration) 
        this.configuration = configuration;
    

    public void parse(InputStream inputStream) throws DocumentException 
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");

        List<Element> list = rootElement.selectNodes("//select");
        for (Element element : list) 
            String id = element.attributeValue("id");
            String resultType = element.attributeValue("resultType");
            String parameterType = element.attributeValue("parameterType");
            String sqlText = element.getTextTrim();
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id).setResultType(resultType)
                    .setParameterType(parameterType)
                    .setSql(sqlText);
            String key = namespace + "." + id;
            configuration.getMappedStatementMap().put(key,mappedStatement);
        
    

创建工厂构建类生成工厂类,以便获取最终的SqlSession类

public class SqlSessionFactoryBuilder 

    public static SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException 
        //1.使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfiguration(inputStream);
        //2.创建sqlSessionFactory:工厂类:生成sqlSession会话对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;
    

创建sqlSessionFactory:工厂类:生成sqlSession会话对象

public interface SqlSessionFactory 
    SqlSession openSession();

public class DefaultSqlSessionFactory implements SqlSessionFactory


    private Configuration configuration;

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

    @Override
    public SqlSession openSession() 
        return new DefaultSqlSession(this.configuration);
    

定义SqlSession的操作方法,这里只定义查询

public interface SqlSession 

    /**
     * 查询所有
     */
    <E> List<E> selectList(String statementId,Object... params) throws Exception;

    <E> E selectOne(String statementId,Object... params) throws Exception;

statementId代表sql唯一id,params为参数,返回类型不确定,使用到了泛型

SqlSession实现类

public class DefaultSqlSession implements SqlSession 

    private Configuration configuration;

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

    @Override
    public <E以上是关于Java进阶之路-自定义持久层框架的主要内容,如果未能解决你的问题,请参考以下文章

小白的进阶之路6

Java持久层框架Mybatis入门

Java精进-手写持久层框架

Java精进-手写持久层框架

Java精进-手写持久层框架

Mybatis学习之自定义持久层框架 为什么要用框架而不直接用JDBC?