自定義持久層框架

来源:https://www.cnblogs.com/wangshaoyun/archive/2022/04/29/16205105.html
-Advertisement-
Play Games

Spring Boot 1 Spring Boot入門 1.1 Spring Boot特性: 能夠快速創建基於 Spring 的應用程式 能夠直接使用 java main 方法啟動內嵌的 Tomcat 伺服器運行 Spring Boot 程式,不需 要部署 war 包文件 提供約定的 starter ...



自定義持久層框架

下圖是JDBC引起的一系列問題以及解決辦法:

 

 

自定義持久層框架設計思路:


使用端(項目):引入自定義持久層框架jar包。

提供兩部分配置信息:1,資料庫配置信息;2,sql配置信息--(sql語句、參數類型、返回值類型)
解決辦法:使用配置文件來提供兩部分配置信息:
<1>sqlMapConfig.xml:放資料庫配置信息;在sqlMapConfig.xml中,其實也可以存放mapper.xml的全路徑,方法getResourceAsSteam()可以一次性全部讀取;
<2>mapper.xml:存放sql配置信息;

自定義持久層框架本身(工程):本質上就是對JDBC代碼進行了封裝。

(1)載入配置文件,根據配置文件的路徑,載入配置文件成位元組輸入流,存儲在記憶體中;
創建Resource類
方法:getResourceAsSteam(String path)返回 InputSteam;
(2)創建兩個javaBean(容器對象):存放的就是對配置文件解析出來的內容,如下:
Configruation核心配置類:存放sqlMapConfig.xml解析出來的內容;
MappedStatement映射配置類:存放mapper.xml解析出來的內容;    
(3)解析配置文件,可以採用dom4j對配置文件進行解析;
創建類:SqlSessionFactoryBuilder,方法:build(InputSteam is)
<1>使用dom4j解析配置文件,將解析出來的內容封裝到容器對象中;
<2>創建SqlSessionFactory對象;主要作用就是利用工廠模式生產sqlSession(會話對象)
(4)基於開閉原則創建SqlSessionFactory介面及實現類DefaultSqlSessionFactory
<1>生產sqlSession【openSqlSession()】
(5)創建SqlSession介面及實現類DefaultSession
定義對資料庫的crud操作:selectList()selectOne()update()delete()
(6)創建Exeutor介面及實現類SimpleExeutor實現類,執行的就是JDBC代碼;
query(Configruation,MappedStatement,Object ... params);

 

創建兩個maven工程IPersistence和IPersistence_test

--IPersistence_test引入IPersistence依賴--

    <groupId>com.yun</groupId>
  <artifactId>IPersistence_test</artifactId>
  <version>1.0-SNAPSHOT</version>

  <!--引入自定義持久層框架依賴-->
  <dependencies>
      <dependency>
          <groupId>com.yun</groupId>
          <artifactId>IPersistence</artifactId>
          <version>1.0-SNAPSHOT</version>
      </dependency>
  </dependencies>

--IPersistence_test-->sqlMapConfig.xml--
<configuration>
    <!--資料庫配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://xxx.xxx.xx.xxx:xxxx/xxxx"></property>
        <property name="username" value="xxxx"></property>
        <property name="password" value="xxxx"></property>
    </dataSource>

    <!--存放mapper.xml的全路徑-->
    <mapper resource="userMapper.xml"></mapper>

</configuration>
--IPersistence_test-->userMapper.xml--
<mapper namespace="user">

    <!--sql的唯一標識應該是由 namespace.id來組成(statementId)-->
    <select id="selectList" resultType="com.yun.pojo.User">
        select * from user
    </select>

    <!--利用反射獲取到user對象的參數-->
    <select id="selectOne" resultType="com.yun.pojo.User" paramterType="com.yum.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

</mapper>

--IPersistence--
@Data
public class MappedStatement {

    //id
    private Integer id;

    //返回值類型
    private String resultType;

    //參數值類型
    private String paramterType;

    //sql語句
    private String sql;
}
--IPersistence--
@Data
public class Configuration {

    private DataSource dataSource;

    /**
     * k:statementId
     * v:封裝好的MappedStatement對象
     */
    Map<String,MappedStatement> map = new HashMap<>();
}

按照設計思路編寫代碼


--解析配置文件返迴流
public class Resources {

    /**
     * 根據配置文件的路徑,將配置文件載入成位元組輸入流,存儲在記憶體中
     * @param path
     * @return
     */
    public static InputStream getResourcesAsSteam(String path){
        InputStream resourceAsStream = Resources.class.getClassLoader().getResourceAsStream(path);
        return resourceAsStream;
    }

}
@Data
public class Configuration {

    private DataSource dataSource;

    /**
     * k:statementId
     * v:封裝好的MappedStatement對象
     */
    Map<String,MappedStatement> map = new HashMap<>();

}
@Data
public class MappedStatement {

    //id
    private String id;

    //返回值類型
    private String resultType;

    //參數值類型
    private String paramterType;

    //sql語句
    private String sql;
}
--將解析的流封裝到SqlSessionFactoryBuilder中
public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream is) throws Exception {
        //1,使用dom4j解析配置文件,將解析出來的內容封裝到Configuration中
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(is);

        //2,創建sqlSessionFactory對象,工廠類:生產sqlSession繪畫對象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);

        return defaultSqlSessionFactory;
    }
}
--將sqlMapConfig.xml和userMapper.xml流放入configuration中
public class XmlConfigBuilder {

    private Configuration configuration;

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

    /**
     * 該方法就是使用dom4j將配置文件解析,封裝Configuration
     * @param is
     * @return
     */
    public Configuration parseConfig(InputStream is) throws Exception {

        Document document = new SAXReader().read(is);
        //獲取Configuration根對象<Configuration>
        Element rootElement = document.getRootElement();
        //獲取sqlMapConfig.xml裡面的配置信息並且遍歷
        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);
        }

        //創建 c3p0 連接池
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setUser(properties.getProperty("password"));

        configuration.setDataSource(comboPooledDataSource);

        //mapper.xml解析 步驟:拿到路徑-->載入成位元組輸入流-->dom4j進行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");

        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourcesAsSteam = Resources.getResourcesAsSteam(mapperPath);
            XmlMApperBuilder xmlMApperBuilder = new XmlMApperBuilder(configuration);
            xmlMApperBuilder.prase(resourcesAsSteam);
        }

        return configuration;
    }
}
public class XmlMApperBuilder {

    private Configuration configuration;

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

    public void prase(InputStream is) throws Exception {
        Document document = new SAXReader().read(is);
        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 paramterType = element.attributeValue("paramterType");
            String sqlText = element.getTextTrim();

            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParamterType(paramterType);
            mappedStatement.setSql(sqlText);

            //key值是由 namespace.id來組成
            String key = namespace +"."+id;
            configuration.getMap().put(key,mappedStatement);
        }
    }
}
--利用工廠模式生產sqlSession
public class DefaultSqlSessionFactory implements SqlSessionFactory{

    private Configuration configuration;

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

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

    private Configuration configuration;

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {

        //將要去完成對 SimpleExecutor 里的query方法的調用
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = configuration.getMap().get(statementId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);

        return (List<E>) list;
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (objects.size() == 1) {
            return (T) objects.get(0);
        } else {
            throw new RuntimeException("查詢結果為空或者返回結果過多");
        }
    }
}
--註冊驅動,查詢數據信息 並且封裝返回
public class SimpleExecutor implements Executor {
    @Override                                                                                 //user
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception{
        //1,註冊驅動,獲取連接
        Connection connection = configuration.getDataSource().getConnection();

        //2,獲取sql    select * from user where id = #{id} and username = #{username}
          //轉換sql    select * from user where id = ? and username = ?,轉換過程中,還需要對#{}裡面的值進行存儲解析
        String sql = mappedStatement.getSql();

        BoundSql boundSql = getBoundSql(sql);

        //3,獲取預處理對象
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        //4,設置參數
        //獲取到參數的全路徑
        String paramterType = mappedStatement.getParamterType();
        Class<?> paramterTypeClass =  getClassType(paramterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射根據content獲取到實體對象中的屬性值,再根據屬性值獲取到當前傳過來的參數對象
            Field declaredField = paramterTypeClass.getDeclaredField(content);
            //暴力訪問
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);

            preparedStatement.setObject(i+1,o);
        }

        //5,執行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        //獲取實體對象
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        //獲取實體對象的具體實現
        Object instance = resultTypeClass.newInstance();

        List<Object> objects = new ArrayList<>();

        //6,封裝返回結果集
        while (resultSet.next()) {
            //1,取出元數據
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //獲取欄位名
                String columnName = metaData.getColumnName(i);
                //獲取欄位值
                Object value = resultSet.getObject(columnName);
                //使用反射或者內省根據資料庫表和實體的對應關係,完成封裝
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(instance,value);
            }
            objects.add(instance);
        }

        return (List<E>) objects;
    }

    /**
     * 反射獲取實體
     * @param paramterType
     * @return
     * @throws Exception
     */
    private Class<?> getClassType(String paramterType) throws Exception {
        if (StringUtils.isNullOrEmpty(paramterType)) {
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;
    }

    /**
     * 完成對#{}的解析工作:1,將#{}使用?進行代替;2,解析出#{}裡面的值進行存儲
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //標記處理類:配置標記解析器來完成對占位符的解析處理工作
        ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
        //標記解析器,對占位符的轉換
        GenericTokenParser tokenParser = new GenericTokenParser("#{", "}", tokenHandler);
        //解析出來的sql
        String parseSql = tokenParser.parse(sql);
        //#{}裡面的解析出來的參數名稱
        List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);

        return boundSql;

    }
}

開始測試

public class IPersistenceTest {

    @Test
    public void test() throws Exception {
        //獲取配置文件流
        InputStream resourcesAsSteam = Resources.getResourcesAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourcesAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        //調用
        User user = new User();
        user.setId(1);
        user.setUsername("張三");
        User user2 = sqlSession.selectOne("user.selectOne", user);
        System.out.println(user2);
    }
}

結果:

 

 



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • CSS 陰影的存在,讓物體看上去更加有型立體。 然而,在最簡單的陰影使用之上,我們可以實現更多有意思且更加立體的陰影效果。 本文將帶大家看看如何使用 CSS 實現幾類比普通陰影更加立體的陰影效果。 CSS 陰影基礎 CSS 中,明面上可以實現陰影的有三個屬性: box-shadow - 盒陰影 te ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 對閉包的理解 閉包是指有權訪問另一個函數作用域中變數的函數,創建閉包的最常見的方式就是在一個函數內創建另一個函數,創建的函數可以訪問到當前函數的局部變數。 閉包有兩個常用的用途; 閉包的第一個用途是使我們在函數外部能夠訪問到函數內部 ...
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 對原型、原型鏈的理解 在JavaScript中是使用構造函數來新建一個對象的,每一個構造函數的內部都有一個 prototype 屬性,它的屬性值是一個對象,這個對象包含了可以由該構造函數的所有實例共用的屬性和方法。當使用構造函數新建 ...
  • 一、一體化運營平臺前端請求部分 1.介面封裝 一體化運營平臺採用的是封裝axios的方式來簡化請求介面的使用,在添加新介面時只要調用封裝好的方法就可以發出請求並直接得到解析後的數據 2.整個過程解析 (1)請求方法調用request方法並傳需要的參數 sync_collect_diff_data(d ...
  • 大家好,我是半夏👴,一個剛剛開始寫文的沙雕程式員.如果喜歡我的文章,可以關註➕ 點贊 👍 加我微信:frontendpicker,一起學習交流前端,成為更優秀的工程師~關註公眾號:搞前端的半夏,瞭解更多前端知識! 點我探索新世界! 原文鏈接 ==>http://sylblog.xin/archi ...
  • 微服務概覽 微服務是圍繞業務領域建模可獨立發佈的服務。服務封裝了對應功能並可以通過網路被其他服務訪問。 從外部來看,單個微服務被視為一個黑盒子。它使用最合適的協議在一個或多個網路端點(例如,隊列或REST API)上承載業務功能。消費者,無論他們是其他微服務還是其他類型的程式,都通過這些聯網的端點來 ...
  • 在Java 18中,將UTF-8指定為標準Java API的預設字元集。有了這一更改,依賴於預設字元集的API將在所有實現、操作系統、區域設置和配置中保持一致。 做這一更改的主要目標: 當Java程式的代碼依賴於預設字元集時,使其更具可預測性和可移植性。 闡明標準Java API在哪裡使用預設字元集 ...
  • python 學習筆記 變數、運算符與數據類型 變數 在使用變數前,需要現對其賦值 變數名可以包括字母、數字、下劃線,但不能以字母開頭 python 變數名大小寫是敏感的 first = 2 second = 3 third = first + second print(third) # 5 運算符 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...