<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          自己動(dòng)手寫(xiě)一個(gè)持久層框架

          共 24236字,需瀏覽 49分鐘

           ·

          2020-12-11 11:05

          上一篇:
          廣州蛋殼公寓租客跳樓身亡,室友:他剛畢業(yè)沒(méi)工作,房東就趕我們走!微眾銀行緊急公告...

          1. JDBC問(wèn)題分析

          我們來(lái)看一段JDBC的代碼:

          public?static?void?main(String[]?args)?{
          ????????Connection?connection?=?null;
          ????????PreparedStatement?preparedStatement?=?null;
          ????????ResultSet?resultSet?=?null;
          ????????try?{
          ????????????//1.?加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
          ????????????Class.forName("com.mysql.jdbc.Drive");
          ????????????//2.?通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
          ????????????connection?=?DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8",
          ????????????????????"root","root");
          ????????????//3. 定義SQL語(yǔ)句??表示占位符
          ????????????String?sql?=?"SELECT?*?FROM?user?WHERE?username?=??";
          ????????????//4.?獲取預(yù)處理對(duì)象Statement
          ????????????preparedStatement?=?connection.prepareStatement(sql);
          ????????????//5.?設(shè)置參數(shù),第一個(gè)參數(shù)為SQL語(yǔ)句中參數(shù)的序號(hào)(從1開(kāi)始),第二個(gè)參數(shù)為設(shè)置的參數(shù)值
          ????????????preparedStatement.setString(1,"tom");
          ????????????//6.?向數(shù)據(jù)庫(kù)發(fā)出SQL執(zhí)行查詢,查詢出結(jié)果集
          ????????????resultSet?=?preparedStatement.executeQuery();
          ????????????//7.?遍歷查詢結(jié)果集
          ????????????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?(ClassNotFoundException?e)?{
          ????????????e.printStackTrace();
          ????????}?catch?(SQLException?throwables)?{
          ????????????throwables.printStackTrace();
          ????????}
          ????}

          可以看到,直接使用JDBC開(kāi)發(fā)是存在一些問(wèn)題的,我們來(lái)分析下:

          問(wèn)題分析:

          1. 數(shù)據(jù)庫(kù)配置信息存在硬編碼問(wèn)題
          2. 頻繁創(chuàng)建、釋放數(shù)據(jù)庫(kù)鏈接
          //1.?加載數(shù)據(jù)庫(kù)驅(qū)動(dòng)
          Class.forName("com.mysql.jdbc.Drive");
          //2.?通過(guò)驅(qū)動(dòng)管理類獲取數(shù)據(jù)庫(kù)鏈接
          connection?=?DriverManager.getConnection("jdbc:mysql://hocalhost:3306/mybatis?characterEncoding=utf-8","root","root");
          1. sql語(yǔ)句、設(shè)置參數(shù)、獲取結(jié)果集均存在硬編碼問(wèn)題
          //3. 定義SQL語(yǔ)句??表示占位符
          String?sql?=?"SELECT?*?FROM?user?WHERE?username?=??";
          //4.?獲取預(yù)處理對(duì)象Statement
          preparedStatement?=?connection.prepareStatement(sql);
          //5.?設(shè)置參數(shù),第一個(gè)參數(shù)為SQL語(yǔ)句中參數(shù)的序號(hào)(從1開(kāi)始),第二個(gè)參數(shù)為設(shè)置的參數(shù)值
          ?preparedStatement.setString(1,"tom");
          ?//6.?向數(shù)據(jù)庫(kù)發(fā)出SQL執(zhí)行查詢,查詢出結(jié)果集
          ?resultSet?=?preparedStatement.executeQuery();

          ??????int?id?=?resultSet.getInt("id");
          ??????String?userName?=?resultSet.getString("username");
          1. 手動(dòng)封裝返回結(jié)果集 較為繁瑣
          //7.?遍歷查詢結(jié)果集
          while?(resultSet.next()){
          ????int?id?=?resultSet.getInt("id");
          ????String?userName?=?resultSet.getString("username");
          ????//封裝User
          ????user.setId(id);
          ????user.setUserName(userName);
          ?}
          ?System.out.println(user);

          解決思路:

          1. 寫(xiě)在配置文件中
          2. 連接池(c3p0、dbcp、德魯伊...)
          3. 配置文件 (和1放一起嗎?No,經(jīng)常變動(dòng)和不經(jīng)常變動(dòng)的不要放在一起)
          4. 反射、內(nèi)省

          下面根據(jù)這個(gè)解決思路,自己動(dòng)手寫(xiě)一個(gè)持久層框架,寫(xiě)框架之前分析這個(gè)框架需要做什么


          2. 自定義框架思路分析

          使用端(項(xiàng)目):

          1. 引入自定義持久層框架的jar包
          2. 提供兩部分配置信息:
          • 數(shù)據(jù)庫(kù)配置信息
          • SQL配置信息(SQL語(yǔ)句)
          1. 使用配置文件來(lái)提供這些信息:
            1. sqlMapConfig.xml :存放數(shù)據(jù)庫(kù)的配置信息
            2. mapper.xml :存放SQL配置信息

          自定義持久層框架(工程):

          持久層框架的本質(zhì)就是對(duì)JDBC代碼進(jìn)行了封裝

          1. 加載配置文件:根據(jù)配置文件的路徑加載配置文件成字節(jié)輸入流,存儲(chǔ)內(nèi)存中

            Q:getResourceAsStearm方法需要執(zhí)行兩次分別加載sqlMapConfig額和mapper嗎?

            A:可以但沒(méi)必要,我們可以在sqlMapConfig.xml中寫(xiě)入mapper.xml的全路徑即可

            1. 創(chuàng)建Resources類 方法:getResourceAsStream(String path)
          2. 創(chuàng)建兩個(gè)javaBean:(容器對(duì)象):存放的就是配置文件解析出來(lái)的內(nèi)容

            1. Configuration:核心配置類:存放sqlMapConfig.xml解析出來(lái)的內(nèi)容
            2. MappedStatement:映射配置類:存放mapper.xml解析出來(lái)的內(nèi)容
          3. 解析配置文件:使用dom4j

            1. 創(chuàng)建類:SqlSessionFactoryBuilder 方法:build(InputStream in) 這個(gè)流就是剛才存在內(nèi)存中的
            2. 使用dom4j解析配置文件,將解析出來(lái)的內(nèi)容封裝到容器對(duì)象中
            3. 創(chuàng)建SqlSessionFactory對(duì)象;生產(chǎn)sqlSession:會(huì)話對(duì)象(工廠模式 降低耦合,根據(jù)不同需求生產(chǎn)不同狀態(tài)的對(duì)象)
          4. 創(chuàng)建sqlSessionFactory接口及實(shí)現(xiàn)類DefaultSqlSessionFactory

            1. openSession(); 生產(chǎn)sqlSession
          5. 創(chuàng)建SqlSession接口及實(shí)現(xiàn)類DefaultSession

            1. selectList()
            2. selectOne()
            3. update()
            4. delete()
            5. ...
            6. 定義對(duì)數(shù)據(jù)庫(kù)的CRUD操作,例如:
          6. 創(chuàng)建Executor接口及實(shí)現(xiàn)類SimpleExecutor實(shí)現(xiàn)類

            1. query(Configuration con,MappedStatement ms,Object ...param);執(zhí)行JDBC代碼,Object ...param具體的參數(shù)值,可變參;

          3. 創(chuàng)建表并編寫(xiě)測(cè)試類

          SET?NAMES?utf8mb4;
          SET?FOREIGN_KEY_CHECKS?=?0;

          --?----------------------------
          --?Table?structure?for?user
          --?----------------------------
          DROP?TABLE?IF?EXISTS?`user`;
          CREATE?TABLE?`user`??(
          ??`id`?int(11)?NOT?NULL?AUTO_INCREMENT,
          ??`username`?varchar(50)?CHARACTER?SET?utf8?COLLATE?utf8_general_ci?NULL?DEFAULT?NULL,
          ??PRIMARY?KEY?(`id`)?USING?BTREE
          )?ENGINE?=?InnoDB?AUTO_INCREMENT?=?4?CHARACTER?SET?=?utf8?COLLATE?=?utf8_general_ci?ROW_FORMAT?=?Dynamic;

          --?----------------------------
          --?Records?of?user
          --?----------------------------
          INSERT?INTO?`user`?VALUES?(1,?'lucy');
          INSERT?INTO?`user`?VALUES?(2,?'tom');
          INSERT?INTO?`user`?VALUES?(3,?'jack');

          SET?FOREIGN_KEY_CHECKS?=?1;

          1. 創(chuàng)建一個(gè)Maven項(xiàng)目—— Ipersistence_test

          2. 在resource中創(chuàng)建sqlMapConfig.xml 和 UserMapper.xml

          UserMapper.xml

          <mapper?namespace="user">
          ????
          ????<select?id="selectList"?resultType="com.dxh.pojo.User">
          ????????select?*?from?user
          ????select>
          ????<select?id="selectOne"?resultType="com.dxh.pojo.User"?paramterType="com.dxh.pojo.User">
          ????????select?*?from?user?where?id?=?#{id}?and?username?=?#{userName}
          ????select>
          mapper>

          Q:為什么要有namespace和id ?A:當(dāng)一個(gè)*Mapper.xml中有多條sql時(shí),無(wú)法區(qū)分具體是哪一條所以增加 id 如果有UserMapper.xmlProductMapper.xml,假設(shè)他們的查詢的id都為”selectList“,那么將無(wú)法區(qū)分具體是查詢user還是查詢product的。所以增加 namespacenamespace.id 組成sql的唯一標(biāo)識(shí),也稱為statementId

          sqlMapConfig.xml

          <configuration>
          ????
          ????<dataSource>
          ????????<property?name="driverClass"?value="com.mysql.jdbc.Driver">property>
          ????????<property?name="jdbcUrl"?value="jdbc:mysql:///zdy_mybatis">property>
          ????????<property?name="username"?value="root">property>
          ????????<property?name="password"?value="root">property>
          ????dataSource>
          ????
          ????<mapper?resource="UserMapper.xml">mapper>
          configuration>

          4. 開(kāi)始編寫(xiě)持久層框架

          自定義持久層框架(工程):

          本質(zhì)就是對(duì)JDBC代碼進(jìn)行了封裝

          1. 加載配置文件:根據(jù)配置文件的路徑加載配置文件成字節(jié)輸入流,存儲(chǔ)內(nèi)存中
            1. 創(chuàng)建Resources類 方法:getResourceAsStream(String path)
          2. 創(chuàng)建兩個(gè)javaBean:(容器對(duì)象):存放的就是配置文件解析出來(lái)的內(nèi)容
            1. Configuration:核心配置類:存放sqlMapConfig.xml解析出來(lái)的內(nèi)容
            2. MappedStatement:映射配置類:存放mapper.xml解析出來(lái)的內(nèi)容
          3. 解析配置文件:使用dom4j
            1. 創(chuàng)建類:SqlSessionFactoryBuilder 方法:build(InputStream in) 這個(gè)流就是剛才存在內(nèi)存中的
            2. 使用dom4j解析配置文件,將解析出來(lái)的內(nèi)容封裝到容器對(duì)象中
            3. 創(chuàng)建SqlSessionFactory對(duì)象;生產(chǎn)sqlSession:會(huì)話對(duì)象(工廠模式 降低耦合,根據(jù)不同需求生產(chǎn)不同狀態(tài)的對(duì)象)
          4. 創(chuàng)建sqlSessionFactory接口及實(shí)現(xiàn)類DefaultSqlSessionFactory
            1. openSession(); 生產(chǎn)sqlSession
          5. 創(chuàng)建SqlSession接口及實(shí)現(xiàn)類DefaultSession
            1. 定義對(duì)數(shù)據(jù)庫(kù)的CRUD操作
          6. 創(chuàng)建Executor接口及實(shí)現(xiàn)類SimpleExecutor實(shí)現(xiàn)類
            1. query(Configuration con,MappedStatement ms,Object ...param);執(zhí)行JDBC代碼,Object ...param具體的參數(shù)值,可變參;

          我們之前已經(jīng)對(duì)持久層框架進(jìn)行了分析,需要做6部分組成,如下:

          1. 加載配置文件

          我們要把用戶端的配置文件成字節(jié)輸入流并存到內(nèi)存中:

          新建Resource類,提供一個(gè)static InputStream getResourceAsStream(String path)方法,并返回inputstream

          package?com.dxh.io;
          import?java.io.InputStream;

          public?class?Resource?{
          ????//根據(jù)配置文件的路徑,將配置文件加載成字節(jié)輸入流,存儲(chǔ)在內(nèi)存中
          ????public?static?InputStream?getResourceAsStream(String?path){
          ????????InputStream?resourceAsStream?=?Resource.class.getClassLoader().getResourceAsStream(path);
          ????????return?resourceAsStream;
          ????}
          }

          2. 創(chuàng)建JavaBean(容器對(duì)象)

          之前我們說(shuō)到,要把解析出來(lái)的配置文件封裝成對(duì)象。

          • MappedStatement (存放SQL信息)
          • Configuration (存放數(shù)據(jù)庫(kù)配置信息)
          //?MappedStatement,我們存放SQL的信息?
          package?com.dxh.pojo;
          public?class?MappedStatement?{
          ????//?id標(biāo)識(shí)
          ????private?String?id;
          ????//返回值類型
          ????private?String?resultType;
          ????//參數(shù)值類型
          ????private?String?paramterType;
          ????//sql語(yǔ)句
          ????private?String?sql;
          ????
          ??getset省略...
          }

          這里我們把封裝好的MappedStatement對(duì)象也放在Configuration中,同時(shí)我們不存放數(shù)據(jù)庫(kù)的url、username...了,直接存放DataSource

          package?com.dxh.pojo;

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

          public?class?Configuration?{
          ????private?DataSource?dataSource;
          ????/**
          ?????*?key?statementId??(就是namespace.id)
          ?????*?value:封裝好的MappedStatement對(duì)象
          ?????*/

          ????Map?mappedStatementMap?=?new?HashMap<>();
          ?
          ????getset省略...
          }

          3.解析xml文件

          這一步我們解析兩個(gè)xml文件sqlMapConfig.xmlmapper.xml

          我們首先把解析的過(guò)程封裝起來(lái):新建XMLConfigBuild.java

          package?com.dxh.config;

          import?com.dxh.io.Resource;
          import?com.dxh.pojo.Configuration;
          import?com.mchange.v2.c3p0.ComboPooledDataSource;
          import?org.dom4j.Document;
          import?org.dom4j.DocumentException;
          import?org.dom4j.Element;
          import?org.dom4j.io.SAXReader;

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

          public?class?XMLConfigBuild?{
          ????private?Configuration?configuration;

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

          ????/**
          ?????*?該方法就是將配置文件進(jìn)行解析(dom4j),封裝Configuration
          ?????*/

          ????public?Configuration?parseConfig(InputStream?inputStream)?throws?DocumentException,?PropertyVetoException?{
          ????????Document?document?=?new?SAXReader().read(inputStream);
          ????????//
          ????????Element?rootElement?=?document.getRootElement();
          ????????List?list?=?rootElement.selectNodes("http://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.setPassword(properties.getProperty("password"));
          ????????configuration.setDataSource(comboPooledDataSource);

          ????????//mapper.xml解析?:拿到路徑--字節(jié)輸入流---dom4j解析
          ????????List?mapperList?=?rootElement.selectNodes("http://mapper");
          ????????for?(Element?element?:?mapperList)?{
          ????????????//拿到路徑
          ????????????String?mapperPath?=?element.attributeValue("resource");
          ????????????//字節(jié)輸入流
          ????????????InputStream?resourceAsStream?=?Resource.getResourceAsStream(mapperPath);
          ????????????//dom4j解析
          ????????????//??因?yàn)榻馕鐾瓿珊蟮腗appedStatement要放在Configuration里,所以傳入一個(gè)configuration進(jìn)去
          ????????????XMLMapperBuild?xmlMapperBuild?=?new?XMLMapperBuild(configuration);
          ????????????xmlMapperBuild.parse(resourceAsStream);
          ????????}
          ????????return?configuration;
          ????}
          }

          3.1 解析Mapper.xml文件

          package?com.dxh.config;

          import?com.dxh.pojo.Configuration;
          import?com.dxh.pojo.MappedStatement;
          import?org.dom4j.Document;
          import?org.dom4j.DocumentException;
          import?org.dom4j.Element;
          import?org.dom4j.io.SAXReader;

          import?java.io.InputStream;
          import?java.util.List;

          public?class?XMLMapperBuild?{
          ????private?Configuration?configuration;

          ????public?XMLMapperBuild(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?list?=?rootElement.selectNodes("http://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.setParamterType(paramterType);
          ????????????mappedStatement.setResultType(resultType);
          ????????????mappedStatement.setSql(sqlText);
          ????????????String?key?=?namespace+"."+id;
          ????????????configuration.getMappedStatementMap().put(key,mappedStatement);
          ????????}
          ????}
          }

          很容易理解,因?yàn)槲覀兘馕龊笠祷?code style>Configuration對(duì)象,所以我們需要聲明一個(gè)Configuration 并初始化。

          我們把加載文件后的流傳入,通過(guò)dom4j解析,并通過(guò)ComboPooledDataSource(C3P0連接池)生成我們需要的DataSource,并存入Configuration對(duì)象中。

          Mapper.xml解析方式同理。

          3.2 創(chuàng)建SqlSessionFactoryBuilder類:有了上述兩個(gè)解析方法后,我們創(chuàng)建一個(gè)類,用來(lái)調(diào)用這個(gè)方法,同時(shí)這個(gè)類返回SqlSessionFacetory

          SqlSessionFacetory:用來(lái)生產(chǎn)sqlSession:sqlSession就是會(huì)話對(duì)象(工廠模式 降低耦合,根據(jù)不同需求生產(chǎn)不同狀態(tài)的對(duì)象)

          package?com.dxh.sqlSession;

          import?com.dxh.config.XMLConfigBuild;
          import?com.dxh.pojo.Configuration;
          import?org.dom4j.DocumentException;

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

          public?class?SqlSessionFacetoryBuild?{
          ????public?SqlSessionFacetory?build(InputStream?in)?throws?DocumentException,?PropertyVetoException?{
          ????????//1.?使用dom4j解析配置文件,將解析出來(lái)的內(nèi)容封裝到configuration中
          ????????XMLConfigBuild?xmlConfigBuild?=?new?XMLConfigBuild();
          ????????Configuration?configuration?=?xmlConfigBuild.parseConfig(in);

          ????????//2. 創(chuàng)建sqlSessionFactory對(duì)象?工廠類:生產(chǎn)sqlSession:會(huì)話對(duì)象,與數(shù)據(jù)庫(kù)交互的增刪改查都封裝在sqlSession中
          ????????DefaultSqlSessionFactory?sqlSessionFacetory?=?new?DefaultSqlSessionFactory(configuration);
          ????????return?sqlSessionFacetory;
          ????}

          }

          4. 創(chuàng)建SqlSessionFacetory接口和實(shí)現(xiàn)類

          基于開(kāi)閉原則我們創(chuàng)建SqlSessionFacetory接口和實(shí)現(xiàn)類DefaultSqlSessionFactory

          接口中我們定義openSession()方法,用于生產(chǎn)SqlSession

          package?com.dxh.sqlSession;

          public?interface?SqlSessionFacetory?{
          ????public?SqlSession?openSession();
          }
          package?com.dxh.sqlSession;
          import?com.dxh.pojo.Configuration;

          public?class?DefaultSqlSessionFactory?implements?SqlSessionFacetory{
          ????private?Configuration?configuration;

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

          ????@Override
          ????public?SqlSession?openSession()?{
          ????????return?new?DefaultSqlSession(configuration);
          ????}
          }

          同樣我們?cè)?code style>DefaultSqlSessionFactory中傳入Configuration,Configuration需要我們一直往下傳遞

          5.創(chuàng)建SqlSession接口以及它的實(shí)現(xiàn)類

          在接口中,我定義兩個(gè)方法:

          因?yàn)閰?shù)類型和個(gè)數(shù)我們都不知道,所以我們使用泛型,同時(shí),傳入statementId(namespace、. 、id 組成)

          package?com.dxh.sqlSession;
          import?java.util.List;

          public?interface?SqlSession?{
          ????//查詢多條
          ????public??List?selectList(String?statementId,Object...?params)?throws?Exception;
          ????//根據(jù)條件查詢單個(gè)
          ????public??T?selectOne(String?statementId,Object...?params)?throws?Exception;
          }
          package?com.dxh.sqlSession;
          import?com.dxh.pojo.Configuration;
          import?java.util.List;

          public?class?DefaultSqlSession?implements?SqlSession?{
          ????private?Configuration?configuration;

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

          ????@Override
          ????public??List?selectList(String?statementId,?Object...?params)?throws?Exception?{
          ????????//將要完成對(duì)simpleExecutor里的query方法調(diào)用
          ????????SimpleExecutor?simpleExecutor?=?new?SimpleExecutor();
          ????????List?list?=?simpleExecutor.query(configuration,?configuration.getMappedStatementMap().get(statementId),?params);
          ????????return?(List)?list;
          ????}

          ????@Override
          ????public??T?selectOne(String?statementId,?Object...?params)?throws?Exception?{
          ????????List?objects?=?selectList(statementId,?params);
          ????????if?(objects.size()==1){
          ????????????return?(T)?objects.get(0);
          ????????}else{
          ????????????throw?new?RuntimeException("查詢結(jié)果為空或者返回結(jié)果過(guò)多");
          ????????}
          ????}
          }

          這里selectOne方法和selectList方法的參數(shù)結(jié)構(gòu)都是一樣的,所以我們可以通過(guò)selectList.get(0)的方式得到一個(gè)返回結(jié)果。而selectList中則是重點(diǎn),我們需要?jiǎng)?chuàng)建一個(gè)對(duì)象SimpleExecutor并在其中執(zhí)行SQL

          6.創(chuàng)建Executor接口及實(shí)現(xiàn)類SimpleExecutor實(shí)現(xiàn)類

          package?com.dxh.sqlSession;

          import?com.dxh.pojo.Configuration;
          import?com.dxh.pojo.MappedStatement;

          import?java.sql.SQLException;
          import?java.util.List;

          public?interface?Executor?{
          ????/**
          ?????*
          ?????*?@param?configuration?數(shù)據(jù)庫(kù)配置信息
          ?????*?@param?mappedStatement?SQL配置信息
          ?????*?@param?params?可變參
          ?????*?@return
          ?????*/

          ????public??List?query(Configuration?configuration,?MappedStatement?mappedStatement,Object...?params)?throws?SQLException,?Exception;
          }
          package?com.dxh.sqlSession;

          import?com.dxh.config.BoundSql;
          import?com.dxh.pojo.Configuration;
          import?com.dxh.pojo.MappedStatement;
          import?com.dxh.utils.GenericTokenParser;
          import?com.dxh.utils.ParameterMapping;
          import?com.dxh.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;

          /**
          ?*?@author?https://github.com/CoderXiaohui
          ?*?@Description
          ?*?@Date?2020-11-07?22:27
          ?*/

          public?class?SimpleExecutor?implements?Executor{
          ????/**
          ?????*??就是在執(zhí)行JDBC的代碼
          ?????*/

          ????@Override
          ????public??List?query(Configuration?configuration,?MappedStatement?mappedStatement,?Object...?params)?throws?Exception?{
          ????????//1.?注冊(cè)驅(qū)動(dòng),獲取鏈接
          ????????Connection?connection?=?configuration.getDataSource().getConnection();
          ????????//2.?獲取SQL語(yǔ)句
          ????????//假設(shè)獲取的SQL是?:?select?*?from?user?where?id?=?#{id}?and?username?=?#{userName}?JDBC是無(wú)法識(shí)別的,
          ????????//?所以要轉(zhuǎn)換sql :select * from user where id =??and username = ? ,轉(zhuǎn)換過(guò)程中還需要對(duì)#{}中的值進(jìn)行解析存儲(chǔ)
          ????????String?sql?=?mappedStatement.getSql();
          ????????BoundSql?boundSql?=?getBoundSql(sql);
          ????????//3. 獲取預(yù)處理對(duì)象:preparedStatement
          ????????PreparedStatement?preparedStatement?=?connection.prepareStatement(boundSql.getSqlText());
          ????????//4.?設(shè)置參數(shù)
          ????????????//獲取到參數(shù)的全路徑
          ????????String?paramterType?=?mappedStatement.getParamterType();
          ????????Class??paramterTypeClass?=?getClassType(paramterType);
          ????????List?parameterMappingList?=?boundSql.getParameterMappingList();
          ????????for?(int?i?=?0;?i?????????????ParameterMapping?parameterMapping?=?parameterMappingList.get(i);
          ????????????String?content?=?parameterMapping.getContent();
          ????????????//反射
          ????????????Field?declaredField?=?paramterTypeClass.getDeclaredField(content);
          ????????????//暴力訪問(wèn),防止它是私有的
          ????????????declaredField.setAccessible(true);
          ????????????Object?o?=?declaredField.get(params[0]);
          ????????????//下標(biāo)從1開(kāi)始
          ????????????preparedStatement.setObject(i+1,o);
          ????????}
          ????????//5.?執(zhí)行sql
          ????????ResultSet?resultSet?=?preparedStatement.executeQuery();
          ????????String?resultType?=?mappedStatement.getResultType();
          ????????Class?resultTypeClass?=?getClassType(resultType);

          ????????ArrayList?objects?=?new?ArrayList<>();
          ????????//6.?封裝返回結(jié)果集
          ????????while?(resultSet.next()){
          ????????????Object?o?=?resultTypeClass.newInstance();
          ????????????//元數(shù)據(jù)
          ????????????ResultSetMetaData?metaData?=?resultSet.getMetaData();
          ????????????//metaData.getColumnCount()?:查詢結(jié)果的總列數(shù)
          ????????????for?(int?i?=?1;?i?<=?metaData.getColumnCount();?i++)?{
          ????????????????//字段名
          ????????????????String?columnName?=?metaData.getColumnName(i);
          ????????????????//字段的值
          ????????????????Object?value?=?resultSet.getObject(columnName);
          ????????????????//使用反射或者內(nèi)省,根據(jù)數(shù)據(jù)庫(kù)表和實(shí)體的對(duì)應(yīng)關(guān)系,完成封裝
          ????????????????//PropertyDescriptor?內(nèi)省庫(kù)中的一個(gè)類,就是把resultTypeClass中的columnName屬性來(lái)生產(chǎn)讀寫(xiě)方法
          ????????????????PropertyDescriptor?propertyDescriptor?=?new?PropertyDescriptor(columnName,?resultTypeClass);
          ????????????????Method?writeMethod?=?propertyDescriptor.getWriteMethod();
          ????????????????//把具體的值封裝到o這個(gè)對(duì)象中
          ????????????????writeMethod.invoke(o,value);
          ????????????}
          ????????????objects.add(o);
          ????????}
          ????????return?(List)?objects;
          ????}

          ????private?Class?getClassType(String?paramterType)?throws?ClassNotFoundException?{
          ????????if?(paramterType!=null){
          ????????????Class?aClass?=?Class.forName(paramterType);
          ????????????return?aClass;
          ????????}
          ????????return?null;
          ????}

          ????/**
          ?????*?完成對(duì)#{}解析工作:
          ?????*?1.?將#{}使用?進(jìn)行替換
          ?????*?2.?解析出#{}里面的值進(jìn)行存儲(chǔ)
          ?????*?@param?sql
          ?????*?@return
          ?????*/

          ????private?BoundSql?getBoundSql(String?sql)?{
          ????????//標(biāo)記處理類:配置標(biāo)記解析器來(lái)完成對(duì)占位符的處理工作
          ????????ParameterMappingTokenHandler?parameterMappingTokenHandler?=?new?ParameterMappingTokenHandler();
          ????????GenericTokenParser?genericTokenParser?=?new?GenericTokenParser("#{",?"}",?parameterMappingTokenHandler);
          ????????//返回解析后的sql
          ????????String?parseSql?=?genericTokenParser.parse(sql);
          ????????//#{}里面解析出來(lái)的參數(shù)名稱
          ????????List?parameterMappings?=?parameterMappingTokenHandler.getParameterMappings();
          ????????BoundSql?boundSql?=?new?BoundSql(parseSql,parameterMappings);
          ????????return?boundSql;
          ????}
          }
          package?com.dxh.config;
          import?com.dxh.utils.ParameterMapping;
          import?java.util.ArrayList;
          import?java.util.List;
          /**
          *?該方法的作用下面講解
          */

          public?class?BoundSql?{
          ????private?String?sqlText;//解析后的sql
          ????private?List?parameterMappingList?=?new?ArrayList<>();

          ????public?BoundSql(String?sqlText,?List?parameterMappingList)?{
          ????????this.sqlText?=?sqlText;
          ????????this.parameterMappingList?=?parameterMappingList;
          ????}
          }

          這里的實(shí)現(xiàn)大致可分為6部分:

          1. 注冊(cè)驅(qū)動(dòng),獲取鏈接:通過(guò)傳入的configuration得到datasource,然后調(diào)用getConnection()得到鏈接
          2. 獲取SQL語(yǔ)句 我們mapper.xml的SQL語(yǔ)句是這樣的select * from user where id = #{id} and username = #{username},需要轉(zhuǎn)換為select * from user where id = ? and username =? 這樣JDBC才能認(rèn)。同時(shí)我們需要把#{}中的參數(shù)賦值到?這個(gè)占位符處。這里我們定義了一個(gè)getBoundSql方法,通過(guò)標(biāo)記處理類(配置標(biāo)記解析器來(lái)完成對(duì)占位符的處理工作)解析成帶有?的sql,同時(shí)把#{}里面的內(nèi)容傳入ParameterMapping中。
          3. 通過(guò)connection.prepareStatement(boundSql.getSqlText())得到預(yù)處理對(duì)象
          4. 設(shè)置參數(shù),我們?cè)趍apper.xml文件中已經(jīng)寫(xiě)了paramterType,有了入?yún)㈩愋偷娜窂轿覀兛梢酝ㄟ^(guò)反射獲取其對(duì)象。根據(jù)ParameterMapping中存入的的#{}中的內(nèi)容,通過(guò)反射獲取其值,然后與下標(biāo)綁定。
          5. 執(zhí)行SQL
          6. 封裝返回結(jié)果集 這里使用內(nèi)省
          7. 返回(List) objects

          7.結(jié)束

          此時(shí)我們框架中的代碼已經(jīng)寫(xiě)完了。

          8.測(cè)試類

          package?com.dxh.test;

          import?com.dxh.io.Resource;
          import?com.dxh.pojo.User;
          import?com.dxh.sqlSession.SqlSession;
          import?com.dxh.sqlSession.SqlSessionFacetory;
          import?com.dxh.sqlSession.SqlSessionFacetoryBuild;
          import?org.dom4j.DocumentException;
          import?org.junit.Test;

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

          public?class?IPersistenceTest?{

          ????@Test
          ????public?void?test()?throws?Exception?{
          ????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
          ????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
          ????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
          ????????User?user?=?new?User();
          ????????user.setId(1);
          ????????user.setUsername("lucy");

          ????????User?user2?=?sqlSession.selectOne("user.selectOne",user);
          ????????System.out.println(user2.toString());
          //????????List?userList?=?sqlSession.selectList("user.selectList");
          //????????for?(User?user1?:?userList)?{
          //????????????System.out.println(user1);
          //????????}
          ????}
          }

          執(zhí)行結(jié)果:

          User{id=1,?username='lucy'}

          最終的目錄結(jié)構(gòu):

          image-20201108015103475

          5. 自定義持久層框架的優(yōu)化

          我們的自定義持久層框架已經(jīng)完成了,下面我們分析下這個(gè)框架,看看還有沒(méi)有明顯的弊端。

          首先,我們先模仿正常的項(xiàng)目,創(chuàng)建一個(gè)Dao層

          package?com.dxh.dao;
          import?com.dxh.pojo.User;
          import?java.util.List;

          public?interface?IUserDao?{
          ????//查詢所有用戶
          ????public?List?findAll()?throws?Exception;
          ????//根據(jù)條件進(jìn)行查詢
          ????public?User?findByCondition(User?user)?throws?Exception;
          }
          package?com.dxh.dao;

          import?com.dxh.io.Resource;
          import?com.dxh.pojo.User;
          import?com.dxh.sqlSession.SqlSession;
          import?com.dxh.sqlSession.SqlSessionFacetory;
          import?com.dxh.sqlSession.SqlSessionFacetoryBuild;

          import?java.io.InputStream;
          import?java.util.List;

          public?class?IUserDaoImpl?implements?IUserDao?{
          ????@Override
          ????public?List?findAll()?throws?Exception?{
          ????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
          ????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
          ????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
          ????????List?userList?=?sqlSession.selectList("user.selectList");
          ????????return?userList;
          ????}

          ????@Override
          ????public?User?findByCondition(User?user)?throws?Exception?{
          ????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
          ????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
          ????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
          ????????User?user2?=?sqlSession.selectOne("user.selectOne",user);
          ????????return?user2;
          ????}
          }

          問(wèn)題分析:

          1. 很明顯存在代碼重復(fù)的問(wèn)題,他們的前三句話都一樣(加載配置文件、創(chuàng)建SqlSessionFacetory、生產(chǎn)SqlSeesion)

            ?InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
            ?SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
            ?SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
          2. statementId存在硬編碼問(wèn)題

            ?List?userList?=?sqlSession.selectList("user.selectList");
            ?
            ?User?user2?=?sqlSession.selectOne("user.selectOne",user);

          解決思路:

          使用代理模式生成Dao層代理實(shí)現(xiàn)類。

          SqlSession接口中增加一個(gè)方法并實(shí)現(xiàn):

          //為Dao接口生產(chǎn)代理實(shí)現(xiàn)類
          public??T?getMapper(Class?mapperClass);
          ????@Override
          ????public??T?getMapper(Class?mapperClass)?{
          ????????//使用JDK動(dòng)態(tài)代理來(lái)為Dao接口生成代理對(duì)象,并返回
          ????????Object?o?=?Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),?new?Class[]{mapperClass},?new?InvocationHandler()?{
          ????????????@Override
          ????????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
          ????????????????return?null;
          ????????????}
          ????????});
          ????????return?(T)?o;
          ????}

          我們使用Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)方法來(lái)生產(chǎn)代理對(duì)象。一會(huì)我們?cè)賮?lái)實(shí)現(xiàn)invoke方法。

          那么此時(shí)我們?nèi)绻傧雸?zhí)行方法應(yīng)該這樣做:

          IUserDao?iUserDao?=?sqlSession.getMapper(IUserDao.class);
          List?all?=?iUserDao.findAll();
          lll
          1. 通過(guò)sqlSession.getMapper()方法獲得代理對(duì)象
          2. 通過(guò)代理對(duì)象調(diào)用findAll()方法
          3. 執(zhí)行invoke方法

          我們來(lái)看看invoke方法:

          • Object proxy :當(dāng)前代理對(duì)象的引用
          • Method method :當(dāng)前被調(diào)用方法的引用 比如我們當(dāng)前的代理對(duì)象iUserDao調(diào)用的是findAll()方法,而method就是findAll方法的引用
          • Object[] args :傳遞的參數(shù),比如我們想要根據(jù)條件查詢

          編寫(xiě)invoke()方法:

          我們要首先明確一點(diǎn),不論如何封裝,底層都還是執(zhí)行JDBC代碼,那么我們就要根據(jù)不同情況 調(diào)用selectList或者selectOne。

          此時(shí)就有一個(gè)疑問(wèn)了:selectListselectOne都需要一個(gè)參數(shù)——statementId,而此時(shí)我們是拿不到statementId的。

          但是我們可以根據(jù)method對(duì)象得到方法名,和方法所在類的全類名

          因此我們需要規(guī)范下statementId的組成:

          statementId = namespace.id = 方法所在類的全類名.方法名

          修改UserMapper.xml

          image-20201108144050013
          ????@Override
          ????public??T?getMapper(Class?mapperClass)?{
          ????????//使用JDK動(dòng)態(tài)代理來(lái)為Dao接口生成代理對(duì)象,并返回
          ????????Object?o?=?Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(),
          ????????????????new?Class[]
          {mapperClass},?new?InvocationHandler()?{
          ????????????@Override
          ????????????public?Object?invoke(Object?proxy,?Method?method,?Object[]?args)?throws?Throwable?{
          ????????????????//底層都還是執(zhí)行JDBC代碼??//根據(jù)不同情況?調(diào)用selectList或者selectOne
          ????????????????//準(zhǔn)備參數(shù):1. statementId
          ????????????????/**
          ?????????????????*?**此時(shí)就有一個(gè)疑問(wèn)了:`selectList`和`selectOne`都需要一個(gè)參數(shù)——`statementId`,
          ?????????????????*?而此時(shí)我們是拿不到`statementId`的。
          ?????????????????*?但是我們可以根據(jù)`method`對(duì)象得到方法名,和方法所在類的全類名。
          ?????????????????*?因此我們需要規(guī)范下statementId的組成:
          ?????????????????*?**statementId??=??namespace.id??=??方法所在類的全類名.方法名
          ?????????????????*/

          ????????????????String?methodName?=?method.getName();
          ????????????????String?className?=?method.getDeclaringClass().getName();
          ????????????????String?statementId?=?className+"."+methodName;
          ????????????????//準(zhǔn)備參數(shù):2. args
          ????????????????//獲取被調(diào)用方法的返回值類型
          ????????????????Type?genericReturnType?=?method.getGenericReturnType();
          ????????????????//判斷是否進(jìn)行了泛型類型參數(shù)化?就是判斷當(dāng)前的返回值類型是否有泛型
          ????????????????if?(genericReturnType?instanceof?ParameterizedType){
          ????????????????????List?selectList?=?selectList(statementId,?args);
          ????????????????????return?selectList;
          ????????????????}
          ????????????????return?selectOne(statementId,args);
          ????????????}
          ????????});
          ????????return?(T)?o;
          ????}

          測(cè)試:

          package?com.dxh.test;

          import?com.dxh.dao.IUserDao;
          import?com.dxh.io.Resource;
          import?com.dxh.pojo.User;
          import?com.dxh.sqlSession.SqlSession;
          import?com.dxh.sqlSession.SqlSessionFacetory;
          import?com.dxh.sqlSession.SqlSessionFacetoryBuild;
          import?org.dom4j.DocumentException;
          import?org.junit.Test;

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

          public?class?IPersistenceTest?{

          ????@Test
          ????public?void?test()?throws?Exception?{
          ????????InputStream?resourceAsStream?=?Resource.getResourceAsStream("sqlMapConfig.xml");
          ????????SqlSessionFacetory?sqlSessionFacetory?=?new?SqlSessionFacetoryBuild().build(resourceAsStream);
          ????????SqlSession?sqlSession?=?sqlSessionFacetory.openSession();
          ????????IUserDao?iUserDao?=?sqlSession.getMapper(IUserDao.class);
          ????????List?all?=?iUserDao.findAll();
          ????????System.out.println(all);
          ????????//打印結(jié)果:[User{id=1, username='lucy'}, User{id=2, username='李四'}, User{id=3, username='null'}]
          ????????User?user1?=?iUserDao.findByCondition(user);
          ????????System.out.println(user1);
          ???????//User{id=1,?username='lucy'}
          ????}
          }

          來(lái)源:https://www.cnblogs.com/isdxh/p/13953368.html

          作者:DXH's Blog


          最近熬夜給大家準(zhǔn)備了515套Java代碼,有一些是業(yè)務(wù)類的小項(xiàng)目,比如Java博客項(xiàng)目,也有腳手架、也有平時(shí)用一些的工具類、21套小程序代碼,也有一些游戲類的項(xiàng)目。

          掃以下二維碼并回復(fù)“828”即可獲取


          或者在本公眾號(hào)對(duì)話框回復(fù)【828】馬上獲取


          瀏覽 66
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                    <th id="afajh"><progress id="afajh"></progress></th>
                    成人免费视频在线观看 | 亚洲视频在线观看 | 国产男女猛烈无遮挡在线喷水 | 亚洲视频大全 | 欧美艹逼 |