<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      源碼學(xué)習(xí)之路----mybatis

      在開始本文之前呢,我們首先需要了解一下傳統(tǒng)的jdbc存在的問題

      傳統(tǒng)的jdbc的代碼是這樣的:

      代碼 1

      public class Test {
      
          public static final String URL = "jdbc:mysql://localhost:3306/test";
          public static final String USER = "root";
          public static final String PASSWORD = "root";
      
          public static void main(String[] args) throws Exception {
              //1.加載驅(qū)動(dòng)程序
              Class.forName("com.mysql.jdbc.Driver");
              //2. 獲得數(shù)據(jù)庫連接
              Connection conn = DriverManager.getConnection(URL, USER, PASSWORD);
              //3.操作數(shù)據(jù)庫,實(shí)現(xiàn)增刪改查
              Statement stmt = conn.createStatement();
              ResultSet rs = stmt.executeQuery("SELECT user_name, age FROM imooc_goddess");
              //如果有數(shù)據(jù),rs.next()返回true
              while(rs.next()){
                  System.out.println(rs.getString("user_name")+" 年齡:"+rs.getInt("age"));
              }
          }

       

      觀察如上代碼發(fā)現(xiàn) ,傳統(tǒng)的jdbc存在如下問題:

      1.數(shù)據(jù)庫配置信息存在硬編碼。
      2.每次執(zhí)行都要開啟數(shù)據(jù)庫連接,頻繁創(chuàng)建釋放數(shù)據(jù)庫連接。
      3.sql語句存在硬編碼。
      4.需要手動(dòng)封裝返回結(jié)果集,較為繁瑣。
       
      那么就上述問題,我們給出如下的解決方案:
          1)數(shù)據(jù)庫配置信息,以及sql語句的編寫,我們都放入配置文件中,由此以來,方便維護(hù)。
          2)對(duì)于每次都要?jiǎng)?chuàng)建釋放數(shù)據(jù)庫連接,我們使用池技術(shù)來避免頻繁創(chuàng)建釋放數(shù)據(jù)庫連接帶來的資源浪費(fèi)。
          3)返回結(jié)果我們可以使用反射,內(nèi)省的方式進(jìn)行封裝。
       
       
      接下來,我們開始手動(dòng)封裝一個(gè)框架來解決傳統(tǒng)的jdbc問題
       
      對(duì)于使用者來說,我們需要提供數(shù)據(jù)庫的配置信息 和 sql配置信息,所以,需要提供兩個(gè)文件(使用xml來進(jìn)行存放),sqlMapConfig.xml,mapper.xml來存放數(shù)據(jù)庫的配置信息以及sql的配置信息。
       
      代碼 2
      sqlMapConfig.xml
      <configuration>
      
          <!--數(shù)據(jù)庫配置信息-->
          <dataSource>
              <property name="dirverClass" value="com.mysql.jdbc.Driver"></property>
              <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8"></property>
              <property name="username" value="root"></property>
              <property name="password" value="root"></property>
          </dataSource>
      
      <!--    為了一次性將所有的資源全部讀入,此處我們用來存放mapper.xml全路徑-->
          <mapper resource="UserMapper.xml"></mapper>
      </configuration>

       

      代碼 3

      mapper.xml

      說明:

        為了防止我們?cè)谡{(diào)用方法時(shí),不同類中方法名重復(fù),我們?cè)O(shè)置 sql的唯一標(biāo)識(shí)namespace.id =》statementId 來避免這個(gè)問題,
      比如 UserMapper.class中與Order.class中都存在findAll方法,我們就要在userMapper.xml和OrderMapper.xml中都設(shè)置一下namespace屬性,
      這樣 mapper標(biāo)簽的namespace屬性加上select標(biāo)簽的id屬性就可以唯一的確定一個(gè)sql語句了。
      <mapper namespace="user">
      <!--   為了防止我們?cè)谡{(diào)用方法時(shí),不同類中方法名重復(fù),我們?cè)O(shè)置 sql的唯一標(biāo)識(shí)namespace.id =》statementId 來避免這個(gè)問題 
      比如 UserMapper.class中與Order.class中都存在findAll方法,我們就要在userMapper.xml和OrderMapper.xml中都設(shè)置一下namespace屬性,
      這樣 mapper標(biāo)簽的namespace屬性加上select標(biāo)簽的id屬性就可以唯一的確定一個(gè)sql語句了
      --> <select id="findAll" resultType="com.hg.pojo.User"> select * from user </select> <select id="findByCondition" resultType="com.hg.pojo.User" paramterType="com.hg.pojo.User"> select * from user where id = #{id} and username = #{username} </select> </mapper>

      這樣,配置文件就寫好了。

      接下來,我們分析下框架中需要的類,以及實(shí)現(xiàn)方法:

      首先,我們需要加載使用者的配置文件:根據(jù)配置文件的路徑加載配置文件成字節(jié)輸入流,存儲(chǔ)在內(nèi)存中。

      于是,我們創(chuàng)建一個(gè)Resource類來實(shí)現(xiàn)這個(gè)功能

      我們創(chuàng)建一個(gè)maven工程,創(chuàng)建一個(gè)包c(diǎn)om.xx.io來存放我們的Resource類,該類只有一個(gè)方法,就是讀取配置信息成字節(jié)流到內(nèi)存中。

       引入依賴

       <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>

       

      代碼 4

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

      然后我們需要?jiǎng)?chuàng)建兩個(gè)Bean來存放解析出來的數(shù)據(jù)庫配置信息和sql配置信息:

        Configuration:核心配置類:存放sqlMapConfig.xml解析出來的內(nèi)容
        MappedStatement:映射配置類:存放mapper.xml解析出來的內(nèi)容
       
      創(chuàng)建一個(gè)包c(diǎn)om.xx.pojo來存放我們的Configuration類以及MappedStatement類
      代碼 5
      
      
      import javax.sql.DataSource;
      import java.util.HashMap;
      import java.util.Map;

      public
      class Configuration { private DataSource dataSource; 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; } //key statementId value:封裝好的 mappedStatement Map<String,MappedStatement> mappedStatementMap = new HashMap<>(); }

      public class   MappedStatement {
          //id標(biāo)識(shí)
          private String id;
          // 返回值類型
          private String resultType;
          // 參數(shù)值類型
          private String paramterType;
          // sql語句
          private String sql;
      public String getId() {
              return id;
          }
      
          public void setId(String id) {
              this.id = id;
          }
      
          public String getResultType() {
              return resultType;
          }
      
          public void setResultType(String resultType) {
              this.resultType = resultType;
          }
      
          public String getParamterType() {
              return paramterType;
          }
      
          public void setParamterType(String paramterType) {
              this.paramterType = paramterType;
          }
      
          public String getSql() {
              return sql;
          }
      
          public void setSql(String sql) {
              this.sql = sql;
          }
      
      }

      MappedStatement類中,我們定義了四個(gè)屬性,分別對(duì)應(yīng)我們將來解析出來的mapper.xml文件中的id、resultType、paramterType和sql。

      Configuration類中,我們?cè)O(shè)置兩個(gè)屬性,分別是dataSource和mappedStatementMap ,dataSource屬性存放的是數(shù)據(jù)庫的配置信息,mappedStatementMap存放的是sql的信息,這里可以看到,我們的mappedStatementMap是一個(gè)map類型,因?yàn)槲覀兠總€(gè)mapper.xml中都會(huì)存在多個(gè)sql語句,所以,我們?cè)诮馕鰰r(shí),將會(huì)把我們前面提到的用來唯一定位一條sql語句的statementid來當(dāng)做map的key,而解析出來的MappedStatement就是map的value。

      至此,我們就把兩個(gè)配置文件解析好后存放配置信息的類都創(chuàng)建好了,接下來,我們來看一下,這個(gè)配置文件是如何解析的呢?

      我們需要?jiǎng)?chuàng)建類:SQLSessionFactoryBuilder類來解析Resource類讀取來的字節(jié)流中的信息 
      首先,我們?cè)赾om.xx.sqlsession包下創(chuàng)建一個(gè)SqlSession接口,和一個(gè)SqlSessionFactory接口,在SqlSessionFactory接口中我們創(chuàng)建一個(gè)openSession方法用來創(chuàng)建SqlSession
       
      代碼 6
      public interface SqlSession {
      //    查詢所有
          public <E> List<E> selectList(String statementId,Object... params) throws Exception;
          // 根據(jù)條件查詢一條
          public <T> T selectOne(String statementId,Object... params) throws Exception;
      
          public int insert(String statementId,Object... params) throws Exception;
      
          public int delete(String statementId,Object... params) throws Exception;
      
          public int update(String statementId,Object... params) throws Exception;
      
          //為dao接口生成代理實(shí)現(xiàn)類
          public <T> T getMapper(Class<?> mapperClass);
      }
      public interface SqlSessionFactory {
          public SqlSession openSession();
      }
       接下來我們?cè)赾om.xx.sqlsession包下創(chuàng)建SQLSessionFactoryBuilder類用來構(gòu)建一個(gè)SqlSessionFactory:
      代碼 7
      import com.hg.config.XMLConfigBuilder;
      import com.hg.pojo.Configuration;
      import org.dom4j.DocumentException;
      
      import java.beans.PropertyVetoException;
      import java.io.InputStream;
      
      public class SqlSessionFactoryBuilder {
          public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
      //        第一:使用dom4j解析配置文件,將解析出來的內(nèi)容封裝到Configuration中
              XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
              Configuration configuration = xmlConfigBuilder.parseConfig(in);
      
      
      //        第二:創(chuàng)建sqlSessionFactory對(duì)象: 工廠類:生產(chǎn)sqlSession:會(huì)話對(duì)象
              DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
      
              return defaultSqlSessionFactory;
          }
      }

       

      這個(gè)類主要有兩大作用: 
      第一:我們將使用dom4j解析配置文件,將解析出來的內(nèi)容封裝到容器對(duì)象中(Configuration,MappedStatement)
      第二:創(chuàng)建SqlSessionFactory對(duì)象;生產(chǎn)sqlSession:會(huì)話對(duì)象(此處使用的是工廠模式)
      接下來我們?cè)敿?xì)介紹SqlSessionFactoryBuilder的具體實(shí)現(xiàn)
       
      首先我們創(chuàng)建一個(gè)XMLConfigBuilder類來對(duì)sqlMapConfig.xml進(jìn)行解析
      
      
      代碼 8
      import com.hg.io.Resource;
      import com.hg.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 XMLConfigBuilder {
          private Configuration configuration;
      
          public XMLConfigBuilder() {
              this.configuration = new Configuration();
          }
      
          //該方法是使用dom4j將配置文件踐行解析,封裝Configuration
          public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
              Document document = new SAXReader().read(inputStream);
              //拿到 <configuration>
              Element rootElement = document.getRootElement();
              List<Element> 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);
              }
      
              ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
              comboPooledDataSource.setDriverClass(properties.getProperty("dirverClass"));
              comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
              comboPooledDataSource.setUser(properties.getProperty("username"));
              comboPooledDataSource.setPassword(properties.getProperty("password"));
      
              configuration.setDataSource(comboPooledDataSource);
      
      //        mapper.xml解析  拿到路徑 獲取字節(jié)輸入流 進(jìn)行解析
              List<Element> mapperList = rootElement.selectNodes("http://mapper");
              for (Element element : mapperList) {
                  String mapperPath = element.attributeValue("resource");
                  InputStream resourceAsStream = Resource.getResourceAsStream(mapperPath);
                  XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
                  xmlMapperBuilder.parse(resourceAsStream);
              }
      
              return configuration;
          }
      }

      這邊我們?cè)敿?xì)講解一下這個(gè)類:

      首先我們創(chuàng)建一個(gè)午餐的構(gòu)造方法來初始化一個(gè)Configuration類用來存儲(chǔ)接下來解析的所有配置信息。

      然后是創(chuàng)建一個(gè)parseConfig方法來進(jìn)行sqlMapConfig.xml的內(nèi)容解析,這里傳入的參數(shù)就是我們Resource類中獲取到的字節(jié)流,然后通過dom4j的SAXReader開始對(duì)內(nèi)容進(jìn)行解析。
       
      代碼中的document就是獲取到的配置文件內(nèi)容,通過調(diào)用document.getRootElement()獲取到<configuration>中的所有內(nèi)容,然后通過rootElement.selectNodes("http://property");獲取到所有property標(biāo)簽下的內(nèi)容,我們創(chuàng)建一個(gè)Properties來存儲(chǔ)我們接下來解析出來的數(shù)據(jù)庫配置信息,簡(jiǎn)單的來講此處其實(shí)也可以用map來操作;
      代碼中循環(huán)遍歷property并通過element.attributeValue("name")和element.attributeValue("value")將property標(biāo)簽中的內(nèi)容取出存放到Properties中,這樣數(shù)據(jù)庫的配置信息就全部都解析完成了。
       
      接下來我們創(chuàng)建一個(gè)ComboPooledDataSource連接池,并將解析出來的數(shù)據(jù)庫配置信息配置到連接池中,然后將連接池配置到我們的Configuration類的DataSource中。
       

      上面我們提到為了一次性將所有的資源全部讀入我們將mapper.xml文件的的全路徑也配置到了sqlMapConfig.xml中,接下來我們將進(jìn)行mapper.xml 的解析

      我們通過rootElement.selectNodes("http://mapper");來獲取到配置信息中的所有mapper標(biāo)簽,上文代碼中層提到,里面包含著所有的mapper.xml 的路徑信息(因?yàn)槲覀兛赡軙?huì)有多個(gè)mapper文件)。

      接下來我們通過循環(huán)遍歷element.attributeValue("resource")獲取到每一個(gè)mapper.xml的路徑,并使用Resource類進(jìn)行字節(jié)流的讀取。

      然后我們需要?jiǎng)?chuàng)建一個(gè)XMLMapperBuilder類來對(duì)每個(gè)mapper.xml進(jìn)行處理。我們將Configuration作為參數(shù)傳遞進(jìn)去是為了將解析后的mapper.xml信息存儲(chǔ)到Configuration里面的mappedStatementMap集合中。

      然后我們調(diào)用XMLMapperBuilder的parse方法來處理讀取到的mapper.xml文件字節(jié)流。

      好了,接下來我們開始研究下如何將mapper.xml文件的信息解析出來。

      我們先創(chuàng)建XMLMapperBuilder類 

      代碼 9

      package com.hg.config;
      import com.hg.pojo.Configuration;
      import com.hg.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 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("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);
              }
          }
      }

      上面解析sqlMapConfig.xml 的代碼中我們?cè)岬?,?Configuration 作為參數(shù)傳遞給 XMLMapperBuilder ,那么 XMLMapperBuilder 代碼中,我們創(chuàng)建了一個(gè)有參的構(gòu)造方法來進(jìn)行參數(shù)的傳遞,接下來我們創(chuàng)建一個(gè)parse方法來進(jìn)行mapper.xml文件的解析,這里我們接收到的參數(shù)是 XMLConfigBuilder 中傳遞過來的mapper.xml字節(jié)流,同樣的使用dom4j來進(jìn)行解析,重復(fù)的代碼我不再贅述,這邊解析的是mapper.xml中所有select(insert,update,delete)標(biāo)簽下的所有屬性,包括id,resultType,paramterType以及sql(后面我們還要在MappedStatement中加入一個(gè)標(biāo)記屬性,來判斷我們這個(gè)MappedStatement是增,刪,改,查中的哪種);

      上文我們提到要想準(zhǔn)確的定位一個(gè)sql需要通過 statementid(namespace.id)來進(jìn)行唯一標(biāo)示,所以這邊使用 statementid 作為 mappedStatementMap 的key,而獲取到的select標(biāo)簽下的所有信息封裝成 MappedStatement 作為mappedStatementMap 的value,這樣這個(gè)parse方法就把mapper.xml中的配置信息傳遞到了 Configuration 中的 mappedStatementMap 里。最終 XMLConfigBuilder 中的parseConfig方法將所有配置信息都讀取到了Configuration中并將Configuration返回。這樣上文中的  代碼7  SQLSessionFactoryBuilder 類的第一部分就完成了,有了配置信息,接下來,我們繼續(xù)探討如何來進(jìn)行 SQLSession 的創(chuàng)建以及如何進(jìn)行查詢,對(duì)查詢結(jié)果的解析以及封裝。

      我們創(chuàng)建一個(gè)DefaultSqlSessionFactory來實(shí)現(xiàn)SqlSessionFactory接口

      代碼 10

      public class DefaultSqlSessionFactory implements SqlSessionFactory{
          private Configuration configuration;
      
          public DefaultSqlSessionFactory(Configuration configuration) {
              this.configuration = configuration;
          }
      
          @Override
          public SqlSession openSession() {
              return new DefaultSqlSession(configuration);
          }
      }

      此時(shí),我們要?jiǎng)?chuàng)建一個(gè)構(gòu)造函數(shù)來傳遞我們剛才解析出來的Configuration,然后我們?cè)賱?chuàng)建一個(gè)DefaultSqlSession類來實(shí)現(xiàn)SqlSession接口的方法,同使這個(gè)DefaultSqlSession類也要?jiǎng)?chuàng)建一個(gè)有參的構(gòu)造方法用來傳遞我們的Configuration參數(shù)(上面我們分析過,這個(gè)Configuration參數(shù)很重要,數(shù)據(jù)庫的配置信息以及sql的信息全都存儲(chǔ)在這個(gè)Configuration中),然后我們?cè)贒efaultSqlSessionFactory類的openSession方法中創(chuàng)建這個(gè)DefaultSqlSession實(shí)例對(duì)象。

      接下來我們一起研究一下這個(gè)DefaultSqlSession都完成了什么功能:

      代碼 11

      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 {
      //        將要完成對(duì)simpleExcutor里的query方法
              SimpleExecutor simpleExecutor = new SimpleExecutor();
              MappedStatement mappedStatement = configuration.getMappedStatementMap().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("查詢結(jié)果為空或返回結(jié)果過多");
              }
          }
      
          @Override
          public int insert(String statementId, Object... params) throws Exception {
              return this.update(statementId,params);
          }
      
          @Override
          public int delete(String statementId, Object... params) throws Exception {
              return this.update(statementId,params);
          }
      
      
          @Override
          public int update(String statementId, Object... params) throws Exception {
              SimpleExecutor simpleExecutor = new SimpleExecutor();
              MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
              int rows = simpleExecutor.update(configuration, mappedStatement, params);
              return rows;
          }
      
      
      
          @Override
          public <T> T getMapper(Class<?> mapperClass) {
              //使用動(dòng)態(tài)代理為DAO接口生成代理對(duì)象;并返回
              Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, (proxy, method, args) -> {
      //                底層就是執(zhí)行jdbc  根據(jù)不同情況,調(diào)用  selectList和selectOne
      //                準(zhǔn)備參數(shù)  1statementid  sql的唯一標(biāo)識(shí)  namespace.id=接口權(quán)限定名.方法名
                  System.out.println(DefaultSqlSession.class.getClassLoader());
                  System.out.println(mapperClass.getClassLoader());
                  String methodName = method.getName();
                  String className = method.getDeclaringClass().getName();
                  String statementId = className+"."+methodName;
                  MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
                  SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
                  switch (sqlCommandType){
                      case SELECT:
                          //準(zhǔn)備參數(shù)2:params:args
                          //獲取被調(diào)用方法的返回值類型
                          Type genericReturnType = method.getGenericReturnType();
                          //判斷是否進(jìn)行了泛型類型參數(shù)化
                          if(genericReturnType instanceof ParameterizedType){
                              List<Object> objects = selectList(statementId, args);
                              return objects;
                          }
                          return selectOne(statementId,args);
                      case DELETE:
                          return delete(statementId,args);
                      case INSERT:
                          return insert(statementId,args);
                      case UPDATE:
                          return update(statementId,args);
                      default:
                          return null;
                  }
      
              });
              return (T) proxyInstance;
          }
      }

       我們實(shí)現(xiàn)了SqlSession接口中的所有方法,接下來我們逐步介紹這個(gè)類:

      可以看到,我們的增刪改查方法中都用到一個(gè)SimpleExecutor類,這個(gè)類就是用來實(shí)現(xiàn)我們的核心功能:

        1.數(shù)據(jù)庫的連接

        2.獲取sql語句并進(jìn)行轉(zhuǎn)換(把我們寫的sql語句中的#{}替換成?)

        3.獲取預(yù)處理對(duì)象

        4.設(shè)置參數(shù)(將我們的傳值設(shè)置到預(yù)處理對(duì)象中)

        5.執(zhí)行sql

        6.封裝返回結(jié)果(將查詢到的數(shù)據(jù)庫數(shù)據(jù)封裝成我們的pojo對(duì)象)

      接下來我們將一步一步完成這些功能。

      我們創(chuàng)建一個(gè)執(zhí)行器接口Executor,這里我們提供兩個(gè)方法,query和update,query我們用來查數(shù)據(jù),update我們用來 增 刪 改 數(shù)據(jù)。

      代碼 12

      public interface Executor {
          public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
      
          public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception;
      
      }

      然后我們創(chuàng)建一個(gè)SimpleExecutor類來實(shí)現(xiàn)這個(gè)接口中的方法,后面我們將重點(diǎn)的分析一下查詢方法的實(shí)現(xiàn)

      代碼 13

      public class SimpleExecutor implements Executor {
          @Override
          public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
              //1注冊(cè)驅(qū)動(dòng),獲取連接
              Connection connection = configuration.getDataSource().getConnection();
              //2獲取sql語句 轉(zhuǎn)換sql語句  #{} 轉(zhuǎn)換成 ? 轉(zhuǎn)換的過程對(duì)#{}的值解析出來并存儲(chǔ)
              String sql = mappedStatement.getSql();
              BoundSql boundSql = getBoundSql(sql);
              //3獲取預(yù)處理對(duì)象
              PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
      
              //4設(shè)置參數(shù)
      //        獲取到參數(shù)的全路徑
              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();
                  //反射
                  Field declaredField = paramterTypeClass.getDeclaredField(content);
      //            設(shè)置暴力訪問
                  declaredField.setAccessible(true);
                  Object o = declaredField.get(params[0]);
                  preparedStatement.setObject(i+1,o);
      
              }
      
              //5執(zhí)行sql
              ResultSet resultSet = preparedStatement.executeQuery();
              String resultType = mappedStatement.getResultType();
              Class<?> resultTypeClass = getClassType(resultType);
      
      
              ArrayList<Object> objects = new ArrayList<>();
      
              //6封裝返回結(jié)果集
              while(resultSet.next()){
                  Object o = resultTypeClass.newInstance();
                  //元數(shù)據(jù)
                  ResultSetMetaData metaData = resultSet.getMetaData();
      //            getColumnCount  獲取總列數(shù)  就是屬性的個(gè)數(shù)
                  for (int i = 1; i <= metaData.getColumnCount(); i++) {
                      //字段名
                      String columnName = metaData.getColumnName(i);
                      //字段的值
                      Object value = resultSet.getObject(columnName);
                      //使用反射或者內(nèi)省,根據(jù)數(shù)據(jù)庫表和實(shí)體的對(duì)應(yīng)關(guān)系,完成封裝
                      PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName,resultTypeClass);
                      Method writeMethod = propertyDescriptor.getWriteMethod();
                      writeMethod.invoke(o,value);
                  }
                  objects.add(o);
              }
      
              return (List<E>) objects;
          }
      
      
          @Override
          public int update(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
      
              //1注冊(cè)驅(qū)動(dòng),獲取連接
              Connection connection = configuration.getDataSource().getConnection();
              //2獲取sql語句 轉(zhuǎn)換sql語句  #{} 轉(zhuǎn)換成 ? 轉(zhuǎn)換的過程對(duì)#{}的值解析出來并存儲(chǔ)
              String sql = mappedStatement.getSql();
              BoundSql boundSql = getBoundSql(sql);
              //3獲取預(yù)處理對(duì)象
              PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
      
              //4設(shè)置參數(shù)
      //        獲取到參數(shù)的全路徑
              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();
                  //反射
                  Field declaredField = paramterTypeClass.getDeclaredField(content);
      //            設(shè)置暴力訪問
                  declaredField.setAccessible(true);
                  Object o = declaredField.get(params[0]);
                  preparedStatement.setObject(i+1,o);
      
              }
      
              //5執(zhí)行sql
              int rows = preparedStatement.executeUpdate();
              return rows;
          }
      
      
          private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
              if (paramterType!=null){
                  Class<?> aClass = Class.forName(paramterType);
                  return aClass;
              }
              return null;
          }
      
          //對(duì)#{}進(jìn)行解析,將#{}使用?代替,解析#{}中的值進(jìn)行存儲(chǔ)
          private BoundSql getBoundSql(String sql) {
              //標(biāo)記處理類:配置標(biāo)記解析器來完成對(duì)占位符的解析處理工作
              ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
              //標(biāo)記解析器
              GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
              //用標(biāo)記解析器解析出來的sql
              String parseSql = genericTokenParser.parse(sql);
      //        #{}解析出來的參數(shù)名稱
              List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
              BoundSql boundSql = new BoundSql(parseSql, parameterMappings);
              return boundSql;
          }
      }

       

      我們具體來分析下這個(gè)query方法,首先我們注冊(cè)驅(qū)動(dòng),獲取數(shù)據(jù)庫連接,然后,根據(jù)從MappedStatement中獲取sql語句,然后通過getBoundSql方法來解析我們的sql,實(shí)際上這一步就是來將我們sql中的#{}替換成?并將#{}中的屬性取出并存儲(chǔ)到parameterMappingList,也就是代碼中g(shù)etBoundSql方法的作用,該方法用到的工具類我后面會(huì)貼出來。

      將sql處理好后,我們獲取預(yù)處理對(duì)象,然后開始設(shè)置參數(shù),這里我們將會(huì)用到反射。

      首先我們獲取到入?yún)⒌娜窂?,比如com.xx.User,然后我們通過getClassType這個(gè)方法初始化這個(gè)類,然后我們循環(huán)遍歷 parameterMappingList 取出里面的每一個(gè)參數(shù),并通過反射將我們傳遞進(jìn)來的參數(shù)中的值取出來,并對(duì)應(yīng)到?相應(yīng)的位置。而后,我們執(zhí)行查詢操作,獲取到查詢的結(jié)果。

      既然獲取到結(jié)果,我們就要根據(jù)我們?cè)O(shè)置的resultType來對(duì)結(jié)果集進(jìn)行封裝,我們同樣根據(jù)反射來取得我們r(jià)esultType中的類,因?yàn)槲覀儾樵兊臅r(shí)候,可能是多條,可能是一條,所以我們創(chuàng)建一個(gè)Object集合來進(jìn)行結(jié)果集的存儲(chǔ)和返回。

      這里我們通過while來遍歷結(jié)果集,每一次循環(huán)都是一條數(shù)據(jù),所以我們沒循環(huán)一次結(jié)果集都要?jiǎng)?chuàng)建一個(gè)Object對(duì)象進(jìn)行接收, 每遍歷一次結(jié)果集我們都要取出數(shù)據(jù)源再進(jìn)行每一列,也就是每個(gè)字段的遍歷,此處我們for循環(huán)的 i 是從1開始的,因?yàn)槲覀冊(cè)跀?shù)據(jù)源中取列名是從腳標(biāo)1開始的,我們獲取到列名再通過列名獲取到列值,而后我們通過內(nèi)省來將列的值賦值給我們創(chuàng)建的 Object對(duì)象,每一次for循環(huán)就遍歷賦值一個(gè)列,這樣我們就將每個(gè)結(jié)果集的每一條數(shù)據(jù)都映射給了Object對(duì)象,最終生成一個(gè)集合最終返回給用戶。這樣,我們的Query方法就實(shí)現(xiàn)了。至此,傳統(tǒng)(后面我們還講分析一下傳統(tǒng)方法的不足,以及如何優(yōu)化,代碼中已經(jīng)給出了優(yōu)化的部分)的查詢方法就完成了,我們可以寫測(cè)試類進(jìn)行測(cè)試。我將工具類粘貼在下面,方便我們接下來的測(cè)試。

      public class GenericTokenParser {
      
        private final String openToken; //開始標(biāo)記
        private final String closeToken; //結(jié)束標(biāo)記
        private final TokenHandler handler; //標(biāo)記處理器
      
        public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
          this.openToken = openToken;
          this.closeToken = closeToken;
          this.handler = handler;
        }
      
        /**
         * 解析${}和#{}
         * @param text
         * @return
         * 該方法主要實(shí)現(xiàn)了配置文件、腳本等片段中占位符的解析、處理工作,并返回最終需要的數(shù)據(jù)。
         * 其中,解析工作由該方法完成,處理工作是由處理器handler的handleToken()方法來實(shí)現(xiàn)
         */
        public String parse(String text) {
          // 驗(yàn)證參數(shù)問題,如果是null,就返回空字符串。
          if (text == null || text.isEmpty()) {
            return "";
          }
      
          // 下面繼續(xù)驗(yàn)證是否包含開始標(biāo)簽,如果不包含,默認(rèn)不是占位符,直接原樣返回即可,否則繼續(xù)執(zhí)行。
          int start = text.indexOf(openToken, 0);
          if (start == -1) {
            return text;
          }
      
         // 把text轉(zhuǎn)成字符數(shù)組src,并且定義默認(rèn)偏移量offset=0、存儲(chǔ)最終需要返回字符串的變量builder,
          // text變量中占位符對(duì)應(yīng)的變量名expression。判斷start是否大于-1(即text中是否存在openToken),如果存在就執(zhí)行下面代碼
          char[] src = text.toCharArray();
          int offset = 0;
          final StringBuilder builder = new StringBuilder();
          StringBuilder expression = null;
          while (start > -1) {
           // 判斷如果開始標(biāo)記前如果有轉(zhuǎn)義字符,就不作為openToken進(jìn)行處理,否則繼續(xù)處理
            if (start > 0 && src[start - 1] == '\\') {
              builder.append(src, offset, start - offset - 1).append(openToken);
              offset = start + openToken.length();
            } else {
              //重置expression變量,避免空指針或者老數(shù)據(jù)干擾。
              if (expression == null) {
                expression = new StringBuilder();
              } else {
                expression.setLength(0);
              }
              builder.append(src, offset, start - offset);
              offset = start + openToken.length();
              int end = text.indexOf(closeToken, offset);
              while (end > -1) {////存在結(jié)束標(biāo)記時(shí)
                if (end > offset && src[end - 1] == '\\') {//如果結(jié)束標(biāo)記前面有轉(zhuǎn)義字符時(shí)
                  // this close token is escaped. remove the backslash and continue.
                  expression.append(src, offset, end - offset - 1).append(closeToken);
                  offset = end + closeToken.length();
                  end = text.indexOf(closeToken, offset);
                } else {//不存在轉(zhuǎn)義字符,即需要作為參數(shù)進(jìn)行處理
                  expression.append(src, offset, end - offset);
                  offset = end + closeToken.length();
                  break;
                }
              }
              if (end == -1) {
                // close token was not found.
                builder.append(src, start, src.length - start);
                offset = src.length;
              } else {
                //首先根據(jù)參數(shù)的key(即expression)進(jìn)行參數(shù)處理,返回?作為占位符
                builder.append(handler.handleToken(expression.toString()));
                offset = end + closeToken.length();
              }
            }
            start = text.indexOf(openToken, offset);
          }
          if (offset < src.length) {
            builder.append(src, offset, src.length - offset);
          }
          return builder.toString();
        }
      }
      
      
      public class ParameterMapping {
      
          private String content;
      
          public ParameterMapping(String content) {
              this.content = content;
          }
      
          public String getContent() {
              return content;
          }
      
          public void setContent(String content) {
              this.content = content;
          }
      }
      
      
      public class ParameterMappingTokenHandler implements TokenHandler {
          private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();
      
          // context是參數(shù)名稱 #{id} #{username}
      
          public String handleToken(String content) 
      主站蜘蛛池模板: 青青国产揄拍视频| 国产福利免费在线观看| 线观看的国产成人av天堂| 熟女一区二区中文字幕| 天天躁夜夜躁狠狠喷水| 欧美日韩在线视频| 国产精品无码专区| 久99久热免费视频播放| 精品亚洲没码中文字幕| 免费人成年激情视频在线观看| 一个人免费观看WWW在线视频| 国产成人精品无人区一区| 亚洲精品国产一二三区| 丰满妇女强制高潮18xxxx| 99亚洲男女激情在线观看| 电影在线观看+伦理片| 亚洲av无码之国产精品网址蜜芽 | 亚洲欧美电影在线一区二区| 亚洲精品一区二区三区大桥未久| 人妻系列无码专区69影院| 免费福利视频一区二区三区高清| 欧美日韩一线| 澳门永久av免费网站| 国产无遮挡又黄又爽不要vip软件 国产成人精品一区二区秒拍1o | 中文字幕亚洲综合久久青草| 又黄又爽又色的少妇毛片| 热99久久这里只有精品| 中文字幕v亚洲日本在线电影| 欧美18videosex性欧美tube1080| 丁香五月亚洲综合在线国内自拍| 邵武市| 中文字幕av无码免费一区| 日本道不卡一二三区视频| 久久狠狠高潮亚洲精品夜色| 欧美视频专区一二在线观看| 曰本丰满熟妇xxxx性| 中文成人无字幕乱码精品区| 国产成人一区二区三区在线| 成人国产精品一区二区网站公司 | 日韩人妻精品中文字幕专区| 精品亚洲一区二区三区四区|