框架學(xué)習(xí)筆記:自定義持久層框架的簡單實現(xiàn)

一、分析JDBC操作問題

 public static void main(String[] args) {
        //數(shù)據(jù)庫連接對象
        Connection connection = null;
        //預(yù)處理對象
        PreparedStatement preparedStatement = null;
        //結(jié)果集對象
        ResultSet resultSet = null;

        try {
            //1.加載數(shù)據(jù)庫驅(qū)動
            Class.forName("com.mysql.jdbc.Driver");
            //2.通過驅(qū)動 獲取數(shù)據(jù)庫連接對象
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
            //3.構(gòu)造查詢SQL
            String sql = "select * from user where userName=?";
            //4.通過連接獲取預(yù)處理對象
            preparedStatement = connection.prepareStatement(sql);
            //5.設(shè)置參數(shù)
            preparedStatement.setString(1, "lcg");
            //6.查詢數(shù)據(jù)庫
            resultSet = preparedStatement.executeQuery();
            //7.遍歷查詢結(jié)果,封裝成對象
            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 {
            //關(guān)閉resultSet
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //關(guān)閉preparedStatement
            if (preparedStatement != null) {
                try {
                    preparedStatement.cancel();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            //關(guān)閉connection
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

    }

1.1 問題分析
  1. 數(shù)據(jù)庫配置信息存在硬編碼
    解決:將參數(shù)寫到外部配置文件,然后通過讀取配置文件來獲取連接參數(shù)。
  2. 頻繁創(chuàng)建、釋放數(shù)據(jù)庫連接,造成系統(tǒng)資源的浪費,從而影響系統(tǒng)性能。
    解決:連接池
  3. sql語句preparedStatement傳參存在硬編碼
    解決:將sql寫到外部配置文件,程序加載配置文件,獲取sql。
  4. 對結(jié)果集解析存在硬編碼,sql變化會導(dǎo)致解析結(jié)果的代碼發(fā)生變化
    解決:利用JAVA的反射特性實現(xiàn)sql結(jié)果的解析。
1.2 問題解決思路總結(jié)
  • 使用數(shù)據(jù)庫連接池 統(tǒng)一管理 數(shù)據(jù)庫連接;
  • 將數(shù)據(jù)庫連接信息、sql語句等抽取到xml配置文件中;
  • 使用反射、內(nèi)省等底層技術(shù),自動將實體與表進行屬性和字段的自動映射。

二、自定義框架設(shè)計

2.1 設(shè)計思路

使用端(項目)
需要提供核心配置文件,其中包括數(shù)據(jù)庫配置信息以及sql配置信息;此外,還需引入我們后面寫的自定義框架的jar包;另外,還需要測試使用的實體類以及dao類。
配置文件:

  • sqlMapConfig.xml:存放數(shù)據(jù)庫連接信息,并且需要存放mapper.xml的所有路徑。
  • mapper.xml:存放sql配置信息。

框架端

  1. 讀取配置文件(使用端會將配置文件以流的形式傳遞過來,然后轉(zhuǎn)換成對象)
    A. Configuration類:存放數(shù)據(jù)庫基本信息,并以Map的形式(namespace.id作為key)存儲sql語句;
    B. MappedStatement類:sql的基本信息,包括其id、輸入類型、輸出類型、sql語句。
  2. 解析配置文件
    創(chuàng)建sqlSessionFactoryBuilder類:
    具體方法:public SqlSessionFactory build(InputStream in)
    A. 使用dom4j解析配置文件,將解析出來的內(nèi)容封裝到Configuration中;
    B. 創(chuàng)建sqlSessionFactory對象:生產(chǎn)sqlSession會話對象(工廠模式)
  3. 創(chuàng)建SqlSessionFactory接口及實現(xiàn)類:
    ?法:openSession() : 獲取sqlSession接?的實現(xiàn)類實例對象
  4. 創(chuàng)建sqlSession接?及實現(xiàn)類DefaultSqlSession:主要封裝crud?法
    ?法:
    selectList(String statementId,Object param):查詢所有
    selectOne(String statementId,Object param):查詢單個
  5. 創(chuàng)建Executor接口及其實現(xiàn)類SimpleExecutor實現(xiàn)類
    sqlSessionJDBC代碼封裝在這里。
  6. 使用JDK動態(tài)代理來為Dao接口生成代理對象,通過代理對象調(diào)用方法都會執(zhí)行invoke方法,從而去調(diào)用DefaultSqlSession的具體方法。(代理模式)
2.2 自定義持久層框架實現(xiàn)
2.2.1 使用端(項目)

sqlMapConfig.xml

<configuration>
    <!-- 數(shù)據(jù)庫連接信息 -->
    <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 {
    /**
     * 數(shù)據(jù)源
     */
    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解析配置文件,將解析出來的內(nèi)容封裝到Configuration中
        SqlMapConfigBuilder sqlMapConfigBuilder = new SqlMapConfigBuilder();
        Configuration configuration = sqlMapConfigBuilder.config(in);

        // 第二:創(chuàng)建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("返回結(jié)果過多.");
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        // 使用JDK動態(tài)代理來為Dao接口生成代理對象;通過代理對象調(diào)用方法都會執(zhí)行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.準備參數(shù) statementId
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;
                Type genericReturnType = method.getGenericReturnType();
                // 2.調(diào)用方法
                // ParameterizedType 表示參數(shù)化類型,例如 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.轉(zhuǎn)換sql語句 #{}轉(zhuǎn)占位符?,并獲取參數(shù)列表
        String sql = mappedStatement.getSqlText();
        BoundSql boundSql = getBoundSql(sql);
        // 3.獲取預(yù)處理statement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 4.設(shè)置參數(shù)
        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.執(zhí)行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        // 6.封裝結(jié)果集
        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++) {
                // 獲取參數(shù)名稱
                String columnName = metaData.getColumnName(i);
                // 獲取參數(shù)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);
                }
                // 利用反射以及內(nèi)?。▽傩悦枋銎鳎┩瓿煞庋b
                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);
        // 參數(shù)
        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);
        }
    }
}

運行結(jié)果

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容