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

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

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12
      轉載和引用,請注明原文出處! Fork me on GitHub
      結局很美妙的事,開頭并非如此!

      框架源碼系列十二:Mybatis源碼之手寫Mybatis

      一、需求分析

      1、Mybatis是什么?

      一個半自動化的orm框架(Object Relation Mapping)。

      2、Mybatis完成什么工作?

      在面向對象編程中,我們操作的都是對象,Mybatis框架是一個數據訪問層的框架,幫我們完成對象在數據庫中的
      存、取工作。

      為什么稱為半自動化?

      關系型數據庫的操作是通過SQL語句來完成的,Mybatis在幫我們做對象的存取時,需要我們提供對應的SQL語句,它不自動幫我們生成SQL語句,而只幫我們完成:

      1)對象屬性到SQL語句參數的自動填充;
      2)SQL語句執行結果集到對象的自動提取;
      所以稱為半自動的。而我們了解的另一個ORM框架Hibernate則是全自動的。

      半自動化的不足:我們得辛苦一點編寫SQL語句。
      半自動化的優點:我們可以完全把控執行的SQL語句,可以隨時靈活調整、優化。

      3、為什么要用Mybatis?

      1)mybatis學習、使用簡單
      2)半自動化的優點

      4、為什么要學好Mybatis?

      一線互聯網公司出于性能、調優、使用簡單、完全可控的需要,在數據庫訪問層都是采用Mybatis。

      5 、為什么要用orm框架?

      都是為了提高生產效率,少寫代碼,少寫重復代碼!
      不用orm框架,能用什么來完成數據的存取?
      jdbc
      看看jdbc編程的代碼示例

      package com.study.leesmall.sample.mybatis.jdbc;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.ResultSet;
      import java.sql.SQLException;
      import java.util.ArrayList;
      import java.util.List;
      
      import javax.sql.DataSource;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.jdbc.datasource.DataSourceUtils;
      import org.springframework.stereotype.Component;
      import org.springframework.util.StringUtils;
      
      import com.study.leesmall.sample.mybatis.model.User;
      
      @Component
      public class UserDao {
      
          @Autowired
          private DataSource dataSource;
      
          public void addUser(User user) throws SQLException {
      
              try (
                      // 1、獲取連接
                      Connection conn = DataSourceUtils.getConnection(dataSource);
                      // 2、創建預編譯語句對象
                      PreparedStatement pst = conn.prepareStatement(
                              "insert into t_user(id,name,sex,age,address,phone,wechat,email,account,password) "
                                      + " values(?,?,?,?,?,?,?,?,?,?)");) {
                  // 3、設置參數值
                  int i = 1;
                  pst.setString(i++, user.getId());
                  pst.setString(i++, user.getName());
                  pst.setString(i++, user.getSex());
                  pst.setInt(i++, user.getAge());
                  pst.setString(i++, user.getAddress());
                  pst.setString(i++, user.getPhone());
                  pst.setString(i++, user.getWechat());
                  pst.setString(i++, user.getEmail());
                  pst.setString(i++, user.getAccount());
                  pst.setString(i++, user.getPassword());
      
                  // 4、執行語句
                  int changeRows = pst.executeUpdate();
              }
          }
      
          public List<User> queryUsers(String likeName, int minAge, int maxAge, String sex) throws SQLException {
              // 1、根據查詢條件動態拼接SQL語句
              StringBuffer sql = new StringBuffer(
                      "select id,name,sex,age,address,phone,wechat,email,account,password from t_user where 1 = 1 ");
              if (!StringUtils.isEmpty(likeName)) {
                  sql.append(" and name like ? ");
              }
      
              if (minAge >= 0) {
                  sql.append(" and age >= ? ");
              }
      
              if (maxAge >= 0) {
                  sql.append(" and age <= ? ");
              }
      
              if (!StringUtils.isEmpty(sex)) {
                  sql.append(" and sex = ? ");
              }
      
              try (Connection conn = DataSourceUtils.getConnection(dataSource);
                      PreparedStatement pst = conn.prepareStatement(sql.toString());) {
                  // 2 設置查詢語句參數值
                  int i = 1;
                  if (!StringUtils.isEmpty(likeName)) {
                      pst.setString(i++, "%" + likeName + "%");
                  }
      
                  if (minAge >= 0) {
                      pst.setInt(i++, minAge);
                  }
      
                  if (maxAge >= 0) {
                      pst.setInt(i++, maxAge);
                  }
      
                  if (!StringUtils.isEmpty(sex)) {
                      pst.setString(i++, sex);
                  }
      
                  // 3 執行查詢
                  ResultSet rs = pst.executeQuery();
      
                  // 4、提取結果集
                  List<User> list = new ArrayList<>();
                  User u;
                  while (rs.next()) {
                      u = new User();
                      list.add(u);
                      u.setId(rs.getString("id"));
                      u.setName(rs.getString("name"));
                      u.setSex(rs.getString("sex"));
                      u.setAge(rs.getInt("age"));
                      u.setPhone(rs.getString("phone"));
                      u.setEmail(rs.getString("email"));
                      u.setWechat(rs.getString("wechat"));
                      u.setAccount(rs.getString("account"));
                      u.setPassword(rs.getString("password"));
                  }
      
                  rs.close();
      
                  return list;
              }
          }
      }

      用JdbcTemplate的代碼示例:

      package com.study.leesmall.sample.mybatis.jdbc;
      
      import java.sql.ResultSet;
      import java.sql.SQLException;
      import java.util.ArrayList;
      import java.util.List;
      
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.jdbc.core.JdbcTemplate;
      import org.springframework.jdbc.core.RowMapper;
      import org.springframework.util.StringUtils;
      
      import com.study.leesmall.sample.mybatis.model.User;
      
      //@Component
      public class UserDaoUseJdbcTemplate {
      
          @Autowired
          private JdbcTemplate jdbcTemplate;
      
          public void addUser(User user) throws SQLException {
              String sql = "insert into t_user(id,name,sex,age,address,phone,wechat,email,account,password) "
                      + " values(?,?,?,?,?,?,?,?,?,?)";
              jdbcTemplate.update(sql, user.getId(), user.getName(), user.getSex(), user.getAge(), user.getAddress(),
                      user.getPhone(), user.getWechat(), user.getEmail(), user.getAccount(), user.getPassword());
          }
      
          public List<User> queryUsers(String likeName, int minAge, int maxAge, String sex) throws SQLException {
              // 1、根據查詢條件動態拼接SQL語句
              StringBuffer sql = new StringBuffer(
                      "select id,name,sex,age,address,phone,wechat,email,account,password from t_user where 1 = 1 ");
              List<Object> argList = new ArrayList<>();
              if (!StringUtils.isEmpty(likeName)) {
                  sql.append(" and name like ? ");
                  argList.add("%" + likeName + "%");
              }
      
              if (minAge >= 0) {
                  sql.append(" and age >= ? ");
                  argList.add(minAge);
              }
      
              if (maxAge >= 0) {
                  sql.append(" and age <= ? ");
                  argList.add(maxAge);
              }
      
              if (!StringUtils.isEmpty(sex)) {
                  sql.append(" and sex = ? ");
                  argList.add(sex);
              }
      
              return jdbcTemplate.query(sql.toString(), argList.toArray(), new RowMapper<User>() {
                  public User mapRow(ResultSet rs, int rowNum) throws SQLException {
                      User u = new User();
                      u.setId(rs.getString("id"));
                      u.setName(rs.getString("name"));
                      u.setSex(rs.getString("sex"));
                      u.setAge(rs.getInt("age"));
                      u.setPhone(rs.getString("phone"));
                      u.setEmail(rs.getString("email"));
                      u.setWechat(rs.getString("wechat"));
                      u.setAccount(rs.getString("account"));
                      u.setPassword(rs.getString("password"));
      
                      return u;
                  }
              });
          }
      }

      參數設置代碼、結果集處理代碼、JDBC過程代碼都會大量重復,毫無技術含量!
      那就寫個框架做了它!顯示我們的牛B!

      6、框架確切需求

      1)用戶只需定義持久層接口(dao接口)、接口方法對應的SQL語句。
      2)用戶需指明接口方法的參數與SQL語句參數的對應關系。
      3)用戶需指明SQL查詢結果集與對象屬性的映射關系。
      4)框架完成接口對象的生成,JDBC執行過程

      二、設計 

      1、需求 1

      1)用戶只需定義持久層接口(dao接口)、接口方法對應的SQL語句。

      設計問題:
      1)我們該提供什么樣的方式來讓用戶定義SQL語句?
      2)SQL語句怎么與接口方法對應?
      3)這些SQL語句、對應關系我們框架需要獲取到,誰來獲取?又該如何表示存儲

      1.1 SQL定義方式

      XML方式:獨立于代碼,修改很方便(不需改代碼)
      注解方式:直接加在方法上,零xml配置

      問題:SQL語句可做增、刪、改、查操作,我們是否要對SQL做個區分?
      答:要,因為jdbc中對應有不同的方法 executeQuery executeUpdate

      xml方式定義SQL語句定義方式 :設計增刪改查的元素:

      <!ELEMENT insert(#PCDATA) >
      <!ELEMENT update(#PCDATA) >
      <!ELEMENT delete(#PCDATA) >
      <!ELEMENT select (#PCDATA) > 
      <insert>insert into t_user(id,name,sex,age) values(?,?,?,?)</insert>

      注解方式定義SQL語句定義方式 :設計增刪改查的注解:@Insert @Update @Delete @Select ,注解項定義SQL

      @Documented
      @Retention(RUNTIME)
      @Target({ METHOD })
      public @interface Insert {
          String value();
      }
      @Insert("insert into t_user(id,name,sex,age) values(?,?,?,?)")
      public void addUser(User user);

       1.2 SQL語句與接口方法對應

       xml方式時,如何來映射SQL語句對應的接口方法?

       為元素定義一個id,id的值為對應的類名.方法名,如何?

      <insert id="com.study.leesmall.sample.UserDao.addUser">
               insert into t_user(id,name,sex,age) values(?,?,?,?)
      </insert>

      一個Dao接口中可能會定義很多個數據訪問方法,id這么寫很長,能不能便捷一點?
      這是在做SQL與接口方法的映射,我們來加一個mapper元素,它可包含多個insert、update、delete、select元素,相當于分組,一個接口中定義的分到一組。
      在mapper中定義一個屬性namespace,指定里面元素的名稱空間,namespace的值對應接口類名,里面元素的id對應方法名。

      mybatis-mapper.dtd

      <!ELEMENT mapper (insert* | update* | delete* | select*)+ >
      <!ATTLIST mapper namespace CDATA #IMPLIED >
      <!ELEMENT insert(#PCDATA) >
      <!ELEMENT update(#PCDATA) >
      <!ELEMENT delete(#PCDATA) >
      <!ELEMENT select (#PCDATA) >

       這個xml文件命名為 userDaoMapper.xml,內容如下:

      <mapper namespace="com.study.leesmall.sample.userDao">
          <insert id="addUser">
              insert into t_user(id,name,sex,age) values(?,?,?,?)
          </insert>
      </mapper>

      1.3 映射關系的獲取與表示、存儲

      xml方式:
             解析xml來獲取
      注解方式:
            讀取注解信息

      問題:

      1) 怎么表示?
      得設計一個類來表示從xml、注解獲得的SQL映射信息。

      注意:

      id為唯一id:
      xml方式:id=namespace.id屬性值
      注解方式:id=完整類名.方法名
      2) 怎么存儲得到的MappedStatement?
      這些其實就是一個配置信息,我們定義一個Configuration類:

       

      注意:key 為MappedStatement的id

      3) 得有類來負責解析xml

       

      XmlMapperBuilder負責解析xml文檔(parse方法的resource參數用來指定inputStream的來源),parse方法它調用XMLStatementBuilder來解析里面的parse方法,解析完成以后,把獲得的信息存儲到Configuration里面

       4)mapper中可以讓用戶如何來指定文件位置?

      文件可以是在類目錄下,也可是在文件系統目錄下。如何區分?
      規定:
      類目錄下的方式通過 resource屬性指定;
      文件系統文件通過 url屬性指定,值采用URL 本地文件格式指定:file:///

      <configuration>
          <mappers>
              <mapper resource="com/leesmall/UserMapper.xml"/>
              <mapper url="file:///var/mappers/CourseMapper.xml"/>
          <mappers>
      </configuration>

       定義 mybatis-config.dtd

      <!ELEMENT configuration (mappers?)+ >
      <!ELEMENT mappers (mapper*)>
      <!ELEMENT mapper EMPTY>
      <!ATTLIST mapper
      resource CDATA #IMPLIED
      url CDATA #IMPLIED
      > 

      5) 增加了一個config xml文件,就的有類來解析它。

      增加解析mybatis-config.xml配置文件的類

       

      6) 注解的方式需要獲取SQL映射信息,也得有個類來做這件事

       

      7) 誰來使用MapperAnnotationBuilder?

       Configuration吧,在它里面持有MapperAnnotationBuilder,增加添加Mapper接口類的方法。

       

      8) 用戶如何來指定他們的Mapper接口類?

       1、在mybatis-config.xml的mappers中通過mapper指定?

      <configuration>
          <mappers>
              <mapper resource="com/leesmall/UserMapper.xml"/>
              <mapper url="file:///var/mappers/CourseMapper.xml"/>
          <mappers>
      </configuration>

      如何來區分它是個Mapper接口呢?

      給mapper加一個屬性class來專門指定Mapper類名

      <configuration>
          <mappers>
              <mapper resource="com/leesmall/UserMapper.xml"/>
              <mapper url="file:///var/mappers/CourseMapper.xml"/>
              <mapper class="com.study.leesmall.dao.UserDao" />
          <mappers>
      </configuration>

       mybatis-config.dtd

      <!ELEMENT configuration (mappers?)+ >
      <!ELEMENT mappers (mapper*)>
      <!ELEMENT mapper EMPTY>
      <!ATTLIST mapper
      resource CDATA #IMPLIED
      url CDATA #IMPLIED
      class CDATA #IMPLIED
      >

      問題:
      1、這樣一個一個類來指定,好繁瑣?能不能指定一個包名,包含包下所有接口、子孫包下的接口類?
      2、包含包下所有的接口,好像不是很靈活,能不能讓用戶指定包下所有某類型的接口?
      如是什么類型的類,或帶有某注解的接口。
      好的,這很容易,在mappers元素中增加一個package元素,pacakge元素定義三個屬性

      mybatis-config.dtd

      <!ELEMENT configuration (mappers?)+ >
      
      <!ELEMENT mappers (mapper*,package*)>
      
      <!ELEMENT mapper EMPTY>
      <!ATTLIST mapper
      resource CDATA #IMPLIED
      url CDATA #IMPLIED
      class CDATA #IMPLIED
      >
      
      <!ELEMENT package EMPTY>
      <!ATTLIST package
      name CDATA #IMPLIED
      type CDATA #IMPLIED
      annotation CDATA #IMPLIED
      >
      <configuration>
          <mappers>
              <mapper resource="com/leesmall/UserMapper.xml"/>
              <mapper url="file:///var/mappers/CourseMapper.xml"/>
              <mapper class="com.study.leesmall.dao.UserDao" />
              <package name="com.study.leesmall.mapper" />
              <package name="com.study.leesmall.mapper" type="com.study.leesmall.MapperInterface"/>
              <package name="com.study.leesmall.mapper"
                            annotation="com.study.leesmall.mybtis.annotation.Mapper"/>
              <package name="com.study.leesmall.mapper" type="com.study.leesmall.MapperInterface"
                            annotation="com.study.leesmall.mybtis.annotation.Mapper"/>
          <mappers>
      </configuration>

      為了用戶使用方便,我們給定義一個@Mapper注解,默認規則:指定包下加了@Mapper注解的接口,如何?

      加了package元素,又得在Configuration中增加對應的方法了:

       

      約定俗成的規則:指定包下掃到的@Mapper接口,例如UserDao,還可以在包下定義 UserDao.xml,會被加載解析。

      2 需求2

      需求2、用戶需指明接口方法的參數與語句參數的對應關系。

      2.1 語句參數指定

       看下面的Mapper示例

      @Mapper
      public interface UserDao {
          @Insert("insert into t_user(id,name,sex,age) values(?,?,?,?)")
          void addUser(User user);
      }

      User對象的屬性如何與 values(?)對應?
      靠解析 t_user(id,name,sex,age) 可行嗎?
      難度太大!
      萬一User的name叫xname呢!
      既然靠我們來解析不行,那就請用戶指明吧。用戶如何來指明呢?

      我們來給定個規則: ? 用 #{屬性名} 代替,我們來解析SQL語句中的 #{屬性名} 來決定參數對應。

      @Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})")
      void addUser(User user);

       萬一是這種情況呢?

      @Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})")
      void addUser(String id,String xname,String sex,int age);

      完全可以要求用戶必須與參數名對應 :#{xname}。
      為了提高點自由度(及后面方便SQL復用),可以定義一個注解讓用戶使用,該注解只可用在參數上

       

      @Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})")
      void addUser(String id,@Param("name")String xname,String sex,int age);

       萬一是這種情況呢?

      @Insert("insert into t_user(id,name,sex,age,org_id) values(#{id},#{name},#{sex},#{age},#{id})")
      void addUser(User user,Org org);

       User和Org中都有id屬性,name屬性

       

      好辦,如果方法參數是對象,則以 參數名.屬性 的方式指定SQL參數:

      @Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})")
      void addUser(User user,Org org);

      一樣也可以使用@Param

       如果方法參數是這種情況呢?

      @Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#{sex},#{age})")
      void addUser(Map map);

      對應Map中的key

       如果方法參數是這種情況呢?

      @Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})")
      void addUser(Map map,Org org);
      @Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})")
      void addUser(@Param("user")Map map,Org org);

       再來看下下面的場景:

      @Select("select id,name,sex from t_user where sex = #{sex} order by #{orderColumn}")
      List<User> query(String sex, String orderColumn);

      order by #{orderColumn} order by ? 可以嗎?

      不可以,也就是說 方法參數不全是用來做SQL語句的預編譯參數值的,有些是來構成SQL語句的一部分的。

      那怎么讓用戶指定呢?

       一樣,定義個規則: ${屬性名} 表示這里是字符串替換

      @Select("select id,name,sex from t_user where sex = #{sex} order by ${orderColumn}")
      List<User> query(String sex, String orderColumn);

       2.2 SQL中參數映射解析

       問題:

      1) SQL中參數映射解析要完成的是什么工作?

      解析出真正的SQL語句

      獲得方法參數與語句參數的對應關系 : 問號N---哪個參

          public void addUser(User user) throws SQLException {
      
              try (
                      // 1、獲取連接
                      Connection conn = DataSourceUtils.getConnection(dataSource);
                      // 2、創建預編譯語句對象
                      PreparedStatement pst = conn.prepareStatement(
                              "insert into t_user(id,name,sex,age,address,phone,wechat,email,account,password) "
                                      + " values(?,?,?,?,?,?,?,?,?,?)");) {
                  // 3、設置參數值
                  int i = 1;
                  pst.setString(i++, user.getId());
                  pst.setString(i++, user.getName());
                  pst.setString(i++, user.getSex());
                  pst.setInt(i++, user.getAge());
                  pst.setString(i++, user.getAddress());
                  pst.setString(i++, user.getPhone());
                  pst.setString(i++, user.getWechat());
                  pst.setString(i++, user.getEmail());
                  pst.setString(i++, user.getAccount());
                  pst.setString(i++, user.getPassword());
      
                  // 4、執行語句
                  int changeRows = pst.executeUpdate();
              }
          }

       怎么解析?

      @Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#{user.name},#{user.sex},#{user.age},#{org.id})")
      void addUser(@Param("user")Map map,Org org);

       方式有:

      正則表達式
      antlr

      怎么表示?

       問號的index、值來源

       2) 這個解析的工作在何時做好?誰來做好?

      設計怎么來執行一個Mapper接口了
      SqlSession
      SqlSessionFactory

      3. 需求4

      1)用戶只需定義持久層接口(dao接口)、接口方法對應的SQL語句。
      2)用戶需指明接口方法的參數與語句參數的對應關系。
      3)用戶需指明查詢結果集與對象屬性的映射關系。
      4)框架完成接口對象的生成,JDBC執行過程。

      3.1 SQL語句有了,參數對應關系也有了,我們想想怎么執行SQL吧。

      問題:
      1、要執行SQL,我們得要有DataSource,誰來持有DataSource?

      2、誰來執行SQL? Configuration ?

      不合適,它是配置對象,持有所有配置信息!
      既然是來做事的,那就先定義一個接口吧:SqlSession

       

      該為它定義什么方法呢?

       需求4:框架完成接口對象的生成,JDBC執行過程。

       用戶給定Mapper接口類,要為它生成對象,用戶再使用這個對象完成對應的數據庫操作。

       

      使用示例:

      UserDao userDao = sqlSession.getMapper(UserDao.class);
      userDao.addUser(user);

       來為它定義一個實現類:DefaultSqlSession

       

      它該持有什么屬性嗎?
      用戶給入一個接口類,DefaultSqlSession中就為它生成一個對象?
      萬一給入的不是一個Mapper接口呢?
      也為其生成一個對象就不合理了?
      那怎么判斷給入的接口類是否是一個Mapper接口呢?
      那就只有在配置階段掃描、解析Mapper接口時做個存儲了。
      存哪,用什么存?
      這也是配置信息,還是存在Configuration 中,就用個Set來存吧。

       

      DefaultSqlSession中需要持有Configuration

       

      3.2 對象生成

       1、如何為用戶給入的Mapper接口生成對象?

         很簡單,JDK動態代理

      Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] {
      mapperInterface }, invocationHandler);

       寫一版DefaultSqlSession的實現:

      package com.study.leesmall.mybatis.session;
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Proxy;
      import com.study.leesmall.mybatis.config.Configuration;
      public class DefaultSqlSession implements SqlSession {
          private Configuration configuration;
          
          public DefaultSqlSession(Configuration configuration) {
              super();
              this.configuration = configuration;
          }
          
          @Override
          public <T> T getMapper(Class<T> type) {
              //檢查給入的接口
              if (!this.configuration.getMappers().contains(type)) {
              throw new RuntimeException(type + " 不在Mapper接口列表中!");
              }
              //得到 InvocationHandler
              InvocationHandler ih = null; // TODO 必須要有一個
              // 創建代理對象
              T t = (T)Proxy.newProxyInstance(type.getClassLoader(), new
              Class<?>[] {type}, ih);
              return t;
          }
      }

      問題:每次調用getMapper(Class type)都需要生成一個新的實例嗎?

      代理對象中持有InvocationHandler,如果InvocationHandler能做到線程安全,就只需要一個實例。
      還得看InvocationHandler,先放這吧,把InvocationHandler搞定先

      3.3 執行SQL的InvocationHandler

      了解 InvocationHandler
      InvocationHandler 是在代理對象中完成增強。我們這里通過它來執行SQL。

      public interface InvocationHandler {
          /**
          @param proxy 生成的代理對象
          @param method 被調用的方法
          @param args
          @return Object 方法執行的返回值
          */
          public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable;
      }

      來實現我們的InvocationHandler: MapperProxy

      package com.study.leesmall.mybatis.session;
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      public class MapperProxy implements InvocationHandler {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
          // TODO 這里需要完成哪些事?
          return null;
      }
      }

       思考: 在 MapperProxy.invoke方法中需要完成哪些事?

      package com.study.leesmall.mybatis.session;
      import java.lang.reflect.InvocationHandler;
      import java.lang.reflect.Method;
      public class MapperProxy implements InvocationHandler {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args)
          throws Throwable {
              // TODO 這里需要完成哪些事?
              // 1、獲得方法對應的SQL語句
              
              // 2、解析SQL參數與方法參數的對應關系,得到真正的SQL與語句參數值
              
              // 3、獲得數據庫連接
              
              // 4、執行語句
              
              // 5、處理結果
              
              return null;
          }
      }

      1、獲得方法對應的SQL語句
        要獲得SQL語句,需要用到Configuration,MapperProxy中需持有Configuration

       

      問題:id怎么得來?
      id是類名.方法名。從invoke方法的參數中能得來嗎?

      public Object invoke(Object proxy, Method method, Object[] args)

      method參數能得到方法名,但得到的類名不是Mapper接口類名。
      那就直接讓MapperProxy持有其增強的Mapper接口類吧!簡單!

       

      2、解析SQL參數與方法參數的對應關系,得到真正的SQL與語句參數值

      邏輯:
      1)查找SQL語句中的 #{屬性} ,確定是第幾個參數,再在方法參數中找到對應的值,存儲下來,替換 #{屬性} 為? 。
      2)查找SQL語句中的 ${屬性} ,確定是哪個參數,再在方法參數中找到對應的值,替換 ${屬性} 。
      3)返回最終的SQL與參數數組。

       解析過程涉及的數據:SQL語句、方法的參數定義、方法的參數值

      @Insert("insert into t_user(id,name,sex,age) values(#{id},#{name},#
      {sex},#{age})")
      void addUser(String id,@Param("name")String xname,String sex,int age);
      Parameter[] params = method.getParameters();
      public Object invoke(Object proxy, Method method, Object[] args)

       

       

      說明: 這里是要確定SQL中的?N是哪個參數值。
      這里要分三種情況: 方法參數是0參數,單個參數、多個參數。
      0參數:不需要考慮參數了。
      單個參數:SQL中的參數取值參數的屬性或就是參數本身。
      多個參數:則需要確定SQL中的參數值取第幾個參數的值。

       多個參數的情況,可以有兩種做法:

      方式一: 查找#{屬性},根據Parameter[]中的名稱(注解名、序號)匹配確定是第幾個參數,再到 Object[] args中取到對應的值。
      方式二:先將Parameter[] 和 Object[] args轉為Map,參數名(注解名、序號)為key,Object參數值為值;然后再查找SQL語句中的 #{}${},根據里面的名稱到map中取對應的值。

      哪種方式更好呢?
      我們來看下兩者查找過程的輸出:

       

      方式一相較于方式二,看起來復雜的地方是要遍歷Parameter[] 來確定索引號。

      思考一下:這種查找對應關系的事,需要每次調用方法時都做嗎?方法的參數會有很多個嗎?

      這個對應關系可以在掃描解析Mapper接口時做一次即可。在調用Mapper代理對象的方法時,
      就可以直接根據索引號去Object[] args中取參數值了。

      方式2則每次調用Mapper代理對象的方法時,都需要創建轉換Map。

      而且方式一,單個參數與多個參數我們可以同樣處理。

       要在掃描解析Mapper接口時做參數解析我們就需要定義對應的存儲結構,及修改MappedStatement了

      ?N--- 參數索引號 的對應關系如何表示?
      ?N 就是一個數值,而且是一個順序數(只是jdbc中的?是從1開始)。我們完全可以用List來存儲。
      參數索引號,僅僅是個索引號嗎?

      @Insert("insert into t_user(id,name,sex,age,org_id) values(#{user.id},#
      {user.name},#{user.sex},#{user.age},#{org.id})")
      void addUser(User user,Org org);

       它應該是索引號、和里面的屬性兩部分。

       

      解析階段由它們倆完成這件事:

       

      我們在MappedStatement中再增加一個方法來完成根據參數映射關系得到真正參數值的方法:

       

      把MapperProxy的invoke方法填填看:

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws
      Throwable {
          // TODO 這里需要完成哪些事?
          // 1、獲得方法對應的SQL語句
          String id = this.mapper.getName() + "." + method.getName();
          MappedStatement ms = this.configuration.getMappedStatement(id);
          
          // 2、解析SQL參數與方法參數的對應關系,得到真正的SQL與語句參數值
          RealSqlAndParamValues rsp = ms.getRealSqlAndParamValues(args);
          
          // 3、獲得數據庫連接
          Connection conn =
          this.configuration.getDataSource().getConnection();
          
          // 4、執行語句。
          PreparedStatement pst = conn.prepareStatement(rsp.getSql());
          // 疑問:語句一定是PreparedStatement?
          // 設置參數
          if (rsp.getParamValues() != null) {
              int i = 1;
              for (Object p : rsp.getParamValues()) {
                  pst.setxxx(i++, p); //這里寫不下去了.......如何決定該調用pst的哪
                  個set方法?
              }
              
          }
          
          // 5、處理結果
          
          return null;
      }

      4 JavaType、JdbcType轉換

       1 認識它們

      JavaType:java中的數據類型。
      JdbcType:Jdbc規范中根據數據庫sql數據類型定義的一套數據類型規范,各數據庫廠商遵照這套規范來提供jdbc驅動中數據類型支持。

       

      疑問:為什么我們在這里需要考慮它呢?
      pst.setxxx(i++, p),我們不能根據p的類型選擇set方法嗎?
      看pst的set方法中與對應的:

       

      我們判斷p的類型,然后選擇不可嗎? 像下面這樣

      int i = 1;
      for (Object p : rsp.getParamValues()) {
          if (p instanceof Byte) {
              pst.setByte(i++, (Byte) p);
          } 
          else if (p instanceof Integer) {
              pst.setInt(i++, (int) p);
          }
          else if (p instanceof String) {
              pst.setString(i++, (String) p);
          }
          ...
          else if(...)
      }

      我們來看一下這種情況:

       

      看PreparedStatment的set方法:

       

      上面前兩種情況怎么處理?
      這個需要用戶說明其要使用的JDBCType,不然鬼知道他想要什么。
      讓用戶怎么指定呢?

      #{user.id,jdbcType=TIME}

       

       

      javaType有需要指定不呢?
        好像不需要,那就暫放下。

      第3種情況怎么處理?
      這是一個未知的問題,鬼知道將來使用我的框架的人會需要怎么處理他們的對象呢!
      如何以不變應萬變呢?
      面向接口編程
      定義一個什么樣的接口呢?
      該接口的用途是什么?

      完成Object p 的pst.setXXX()。

      2 TypeHandler

       下面這個if-else-if的代碼是否可以通過TypeHandler,換成策略模式?

      int i = 1;
      for (Object p : rsp.getParamValues()) {
          if (p instanceof Byte) {
              pst.setByte(i++, (Byte) p);
          } 
          else if (p instanceof Integer) {
              pst.setInt(i++, (int) p);
          }
          else if (p instanceof String) {
              pst.setString(i++, (String) p);
          }
          ...
          else if(...)
      }

      定義一些常用數據類型的TypeHandler.
      先不急著去定義,我們來考慮一下下面的問題。
      這個怎么使用它呢?
      在MapperProxy.invoke()中?

      int i = 1;
      for (Object p : rsp.getParamValues()) {
          TypeHandler th = getTypeHandler(p.getClass);//還需要別的參數嗎?
          th.setParameter(pst,i++,p);
      }

       還需要JDBCType。

      int i = 1;
      for (Object p : rsp.getParamValues()) {
          TypeHandler th = getTypeHandler(p.getClass,jdbcType);//jdbcType 從哪來?
          th.setParameter(pst,i++,p);
      }

       

      問題:
      1、是在invoke方法中來判斷TypeHandler呢?還是在MappedStatement的getRealSqlAndParamValues時就返回值對應的TypeHandler?

      選擇后者更合適!
      那SqlAndParamValues中的參數值就不能是Object[]。它是值和TypeHandler兩部分構成。

       

      MapperProxy中的代碼就變成下面這樣了:

      int i = 1;
      for (ParamValue p : rsp.getParamValues()) {
          TypeHandler th = p.getTypeHandler()
          th.setParameter(pst,i++,p.getValue());
      }

       2、MappedStatement又從哪里去獲取TypeHandler呢?

      我們會定義一些常用的,用戶可能會提供一些。用戶怎么提供?存儲到哪里?
      Configuration 吧,它最合適了。以什么結構來存儲呢?
      這里涉及到查找,需要根據 參數的javaType、jdbcTyp來查找。
      那就定義一個map吧,如下這樣如何?

      Map<Type,Map<JDBCType,TypeHandler>> typeHandlerMap;

      我們定義一個TypeHandlerRegistry類來持有所有的TypeHandler,Configuration 中則持有TypeHandlerRegistry

       

       同時我們完善一下TypeHandler

       

      3、用戶如何來指定它們的TypeHandler?

      在mybatis-config.xml中增加一個元素來讓用戶指定吧。
      mybatis-config.dtd

      <!ELEMENT configuration (mappers?, typeHandlers?)+ >
      
      <!ELEMENT mappers (mapper*,package*)>
      
      <!ELEMENT mapper EMPTY>
      <!ATTLIST mapper
      resource CDATA #IMPLIED
      url CDATA #IMPLIED
      class CDATA #IMPLIED
      >
      
      <!ELEMENT package EMPTY>
      <!ATTLIST package
      name CDATA #IMPLIED
      type CDATA #IMPLIED
      annotation CDATA #IMPLIED
      >
      
      <!ELEMENT typeHandlers (typeHandler*,package*)>
      
      <!ELEMENT typeHandler EMPTY>
      <!ATTLIST typeHandler
      class CDATA #REQUIRED
      >

      既可以用typeHandler指定單個,也可用package指定掃描的包,掃描包下實現了TypeHandler接口的類

       mybatis-config.xml

      <configuration>
          <mappers>
              <mapper resource="com/leesmall/UserMapper.xml"/>
              <mapper url="file:///var/mappers/CourseMapper.xml"/>
              <mapper class="com.study.leesmall.dao.UserDao" />
              <package name="com.study.leesmall.mapper" />
          <mappers>
          <typeHandlers>
              <typeHandler class="com.study.leesmall.type.XoTypeHandler" />
              <package name="com.study.leesmall.type" />
          </typeHandlers>
      </configuration>

       解析注冊的工作就交給XMLConfigBuilder

       

      4、MappedStatement中來決定TypeHandler,它就需要Configuration

       

       5、可不可以在解析語句參數關系時,就決定好TypeHandler?

      可以。我們在ParameterMap中增加typeHandler屬性。

       

      用戶在SQL語句參數中必須要指定JDBCType嗎?
        常用的數據類型可以不指定,我們可以提供默認的TypeHandler。

      public class StringTypeHandler implements TypeHandler {
          @Override
          public Type getType() {
              return String.class;
          }
          @Override
          public JDBCType getJDBCType() {
              return JDBCType.VARCHAR;
          }
          @Override
          public void setParameter(PreparedStatement pst, int index, Object
          paramValue) throws SQLException {
              pst.setString(index, (String) paramValue);
          }
      }

      用戶在SQL中參數定義沒有指定JDBCType,則我們可以直接使用我們默認的TypeHandler
      如 #{user.name}
      我們判斷它的參數類型為String,就可以指定它的TypeHandler為 StringTypeHandler。可能它的數據庫類型不為VACHAR,而是一個CHAR定長字符,沒關系!因為pst.setString對VARCHAR、CHAR是通用的。

      5 執行結果處理

       5.1 執行結果處理要干的是什么事

      pst.executeUpate()的返回結果是int,影響的行數。
      pst.executeQuery()的返回結果是ResultSet。
      在得到SQL語句執行的結果后,要轉為方法的返回結果進行返回。這就是執行結果處理要干的事
      根據方法的返回值類型來進行相應的處理。
      這里我們根據SQL語句執行結果的不同,分開處理:

      @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws
      Throwable {
              // TODO 這里需要完成哪些事?
              // 1、獲得方法對應的SQL語句
              String id = this.mapper.getName() + "." + method.getName();
              MappedStatement ms = this.configuration.getMappedStatement(id);
              
              // 2、解析SQL參數與方法參數的對應關系,得到真正的SQL與語句參數值
              RealSqlAndParamValues rsp = ms.getRealSqlAndParamValues(args);
              
              // 3、獲得數據庫連接
              Connection conn =
              this.configuration.getDataSource().getConnection();
              
              // 4、創建語句對象。
              PreparedStatement pst = conn.prepareStatement(rsp.getSql());
              
              // 5、設置語句參數
              int i = 1;
              for (ParamValue p : rsp.getParamValues()) {
                  TypeHandler th = p.getTypeHandler()
                  th.setParameter(pst,i++,p.getValue());
              }
              
              // 6、執行語句并處理結果
              switch (ms.getSqlCommandType()) {
                  case INSERT:
                  case UPDATE:
                  case DELETE:
                      int rows = pst.executeUpdate();
                      return handleUpdateReturn(rows, ms, method);
                  case SELECT:
                      ResultSet rs = pst.executeQuery();
                      return handleResultSetReturn(rs, ms, method);
              }
      }
      
      private Object handleUpdateReturn(int rows, MappedStatement ms, Method
      method) {
          // TODO Auto-generated method stub
          return null;
      }
      
      private Object handleResultSetReturn(ResultSet rs, MappedStatement ms,
      Method method) {
          // TODO Auto-generated method stub
          return null;
      }

      5.2 pst.executeUpate()的返回結果處理

      pst.executeUpate()的返回結果是int
      方法的返回值可以是什么?
      void、int、long 、 其他的不可以!

      private Object handleUpdateReturn(int rows, MappedStatement ms, Method
      method) {
          Class<?> returnType = method.getReturnType();
          if (returnType == Void.TYPE) {
              return null;
          } 
          else if (returnType == int.class || returnType == Integer.class)
          {
              return rows;
          } 
          else if (returnType == long.class || returnType == Long.class) {
              return (long) rows;
          }
          throw new IllegalArgumentException("update類方法的返回值只能是:void/int/Integer/long/Long");
      }

      5.3 pst.executeQuery()的返回結果處理

      pst.executeQuery()的返回結果是ResultSet
      方法的返回值可以是什么?
        可以是void、單個值、集合。
      單個值可以是什么類型的值?
        任意值、(map)

      @Select("select count(1) from t_user where sex = #{sex}")
      int query(String sex);
      @Select(
      "select id,name,sex,age,address from t_user where id = #{id}") User queryUser(String id);
      @Select(
      "select id,name,sex,age,address from t_user where id = #{id}") Map queryUser1(String id);

      集合可以是什么類型?
        List、Set、數組、Vector

      @Select("select id,name,sex,age,address from t_user where sex = #{sex}
      order by #{orderColumn}")
      List<User> query(String sex, String orderColumn);
      
      @Select("select id,name,sex,age,address from t_user where sex = #{sex}
      order by #{orderColumn}")
      List<Map> query1(String sex, String orderColumn);

      集合的元素可以是什么類型的?
        任意類型的,集合只是單個值多做幾遍。
      結果集中的列如何與結果、結果的屬性對應?
        根據結果集列名與屬性名對應
      如果屬性名與列名不一樣呢?
        則需用戶顯式說明映射規則。
      需要考慮JDBCType --- JavaType的處理嗎?

      無論結果是什么類型的,在這里我們都是要完成一件事:從查詢結果中獲得數據返回,只是返回類型不同,有不同的獲取數據的方式。

      請思考:如何讓下面這個方法的代碼的寫好后不再改變?

      private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) {
          // TODO Auto-generated method stub
          return null;
      }

      我需要在此做個抽象,應用策略模式,不同的處理實現這個抽象接口。

      那么在handleResultSetReturn()方法中我們從哪得到ResultHandler呢?

      從MappedStatement 中獲取,每個語句對象(查詢類型的)中都持有它對應的結果處理器。
      在解析準備MappedStatement對象時根據方法的返回值類型選定對應的ResultHandler。

       在handleResultSetReturn方法中只需調用ms中的ResultHandler:

      private Object handleResultSetReturn(ResultSet rs, MappedStatement ms, Object[] args) throws Throwable {
          return ms.getResultHandler().handle(rs, args);
      }

      5.3.1 方法返回單個值

      @Select("select count(1) from t_user where sex = #{sex}")
      int query(String sex);
      
      @Select("select id,name,sex,age,address from t_user where id = #{id}")
      User queryUser(String id);
      
      @Select("select id,name,sex,age,address from t_user where id = #{id}")
      Map queryUser1(String id);

      1、基本數據類型、String 如何處理?

      針對這種情況,提供對應的ResultHandler實現:

      SimpleTypeResultHandler中需要定義什么屬性?
      handle方法中的邏輯該是怎樣的?

      public Object handle(ResultSet rs, Object[] args) throws Throwable {
          //從rs中取對應值
          return rs.getXXX(OOO);
      }

      問題1:該調用rs的哪個get方法?
      得根據返回值來,返回值類型從哪來?
      從SimpleTypeResultHandler中取,在創建MappedStatement時,根據反射獲得的返回值類型給入到SimpleTypeResultHandler中。

       

      SimpleTypeResultHandler的handle方法中的代碼邏輯如下:

      private Object  handle(ResultSet rs, Object[] args) throws Throwable {
          Class<?> returnType = method.getReturnType();
          if (returnType == short.class || returnType == Short.class) 
          {
              return rs.getShort(xxx);
          } 
          else if (returnType == int.class || returnType == Integer.class)
          {
              return rs.getInt(xxx);
          } 
          else if (returnType == long.class || returnType == Long.class) 
          {
              return rs.getLong(xxx);
          }
          ...
          return null;

      問題2:該取結果集中的哪一列?
      如果結果集中只有一列:那就取第1列。
      如果結果集中是有多列呢?

      問題:結果集中應不應該有多列?
      兩種方案:
        1、該返回值情況下不允許結果集多列。
        2、不限制,用戶指定列名。

       

      問題3:這么多if else 合適嗎?

          不合適,咋辦?策略模式
               
          該定義怎樣的策略?
      這是要做什么事情?
        從結果集中獲取值,跟pst.setXXX一樣。
        可不可以在TypeHandler中加方法?

      public interface TypeHandler<T> {
          Type getType();
          
          JDBCType getJDBCType();
          
          void setParameter(PreparedStatement pst, int index, Object paramValue) throws SQLException;
          
          T getResult(ResultSet rs, String columnName) throws SQLException;
          
          T getResult(ResultSet rs, int columnIndex) throws SQLException;
      }
      public class StringTypeHandler implements TypeHandler<String> {
      
          @Override
          public Type getType() {
          return String.class;
          }
          
          @Override
          public JDBCType getJDBCType() {
          return JDBCType.VARCHAR;
          }
          
          @Override
          public void setParameter(PreparedStatement pst, int index, Object
          paramValue) throws SQLException {
              pst.setString(index, (String) paramValue);
          }
          
          @Override
          public String getResult(ResultSet rs, String columnName) throws
          SQLException {
              return rs.getString(columnName);
          }
          
          @Override
          public String getResult(ResultSet rs, int columnIndex) throws
          SQLException {
              return rs.getString(columnIndex);
          }
      }

       一樣的,在啟動解析階段完成結果的TypeHandler選定。

       

      根據返回值類型,從TypeHandlerRegistry中取,要取,還得有JDBCType,用戶可以指定,也可不指定,不指定則使用默認的該類型的TypeHandler。

      默認TypeHandler如何注冊,修改registerTypeHandler方法的定義:

      registerTypeHandler(TypeHandler th,boolean defalut){
          Map<JDBCType,TypeHandler> cmap = typeHandlerMap.get(th.getType);
          
          if(cmap == null)
          {
              cmap = new HashMap<JDBCType,TypeHandler>();
              typeHandlerMap.put(th.getType,cmap);
          }
          camp.put(th.getJDBCType(),th);
          
          if(default)
          {
              cmap.put(DefaultJDBCType.class/null,th);
          }
      }

      很好,那就可以在SimpleTypeResultHandler中持有對應的TypeHandler。
      問:在SimpleTypeResultHandler中還有必要持有Class<?> returnType嗎?
        不需要,在TypeHandler中有了。

       

      SimpleTypeResultHandler 的handle方法代碼就簡單了:

      public Object handle(ResultSet rs, Object[] args) throws Throwable {
          if (StringUtils.isNotEmpty(columnName)) {
              return typeHandler.getResult(rs, columnName);
          } 
          else {
              return typeHandler.getResult(rs, columnIndex);
          }
      }

      2 對象類型返回結果的處理

      @Select("select id,name,sex,age,address from t_user where id = #{id}")
      User queryUser(String id);

      分析:
      1、要完成的事情是什么?
      創建對象
      從結果集中取數據給到對象

      問題:

      1、如何創建對象?

      反射調用構造方法。
      構造方法有多種情況:

      1 未顯式定義構造方法

      public class User {
          private String id;
          private String name;
          private String sex;
          ...
          public String getId() {
          return id;
          }
          public void setId(String id) {
          this.id = id;
          }
          ...
      }

      這種情況不需要考慮什么,直接創建對象!

      2 顯式定義了一個構造方法

      public class User {
          private String id;
          private String name;
          private String sex;
          ...
          
          public User(String id, String name, String sex) {
              super();
              this.id = id;
              this.name = name;
              this.sex = sex;
          }
          
          public String getId() {
              return id;
          }
          
          public void setId(String id) {
              this.id = id;
          }
          ...
      }

      此種情況下,要創建對象,則需要對應的構造參數值。
      問題1:構造參數值從哪來?
        ResultSet

      問題2:怎么知道該從ResultSet中取哪個列的值,取什么類型的值?
      得定義構造參數與ResultSet中列的對應規則,下面的規則是否可以?
      1、優先采用指定列名的方式:用參數名稱當列名、或用戶為參數指定列名(參數名與列名不
      一致時、取不到參數名時);
      2、如不能取得參數名,則按參數順序來取對應順序的列。

      問題3:用戶如何來指定列名?

      注解、xml配置

      public User(@Arg(column="id")String id, @Arg(column="xname")String
      name, @Arg(column="sex")String sex) {
          super();
          this.id = id;
          this.name = name;
          this.sex = sex;
      }
      @Documented
      @Retention(RUNTIME)
      @Target(PARAMETER)
      public @interface Arg {
          String name() default "";
      String column()
      default "";
      Class
      <?> javaType() default void.class;
      JdbcType jdbcType()
      default JdbcType.UNDEFINED;
      Class
      <? extends TypeHandler> typeHandler() default UndefinedTypeHandler.class; }

       

      <resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User">
          <constructor>
              <arg name="" column="" JdbcType="" javaType="" typeHandler=""/>
          </constructor>
      </resultMap>

      mybatis-mapper.dtd 中增加如下定義

      <!ELEMENT resultMap (constructor?)>
      <!ATTLIST resultMap
      id CDATA #REQUIRED
      type CDATA #REQUIRED
      >
      
      <!ELEMENT constructor (arg*)>
      
      <!ELEMENT arg EMPTY>
      <!ATTLIST arg
      javaType CDATA #IMPLIED
      column CDATA #IMPLIED
      jdbcType CDATA #IMPLIED
      typeHandler CDATA #IMPLIED
      name CDATA #IMPLIED
      >

      問題4:這些映射信息得到后如何表示、存儲?
      定義一個結果映射實體:ResultMap

       

       注意,在創建ResultMap時,當用戶沒有指定TypeHandler或是UndefinedTypeHandler時,要根據type、jdbcType取對應的typeHandler,沒有則為null;

      問題5、ResultMap 元素怎么表示?
      ResultMap類定義本身就是表示一種java類型與JDBCType類型的映射,基本數據類型與復合類型(類)都是java類型。

      擴充一下ResultMap即可:

       

      注意:這里有個使用規則需要注意一下:

       如果ResultMap中有TypeHandler,則該結果直接通過調用TypeHandler來獲得。沒有TypeHandler時則看有constructorResultMaps沒,有則根據此取結果集中的值來調用對應的構造方法創建對象。

       3 定義了多個構造方法,怎么辦?

      public class User {
          private String id;
          private String name;
          private String sex;
          ...
          
          public User(String id, String name, String sex) {
              super();
              this.id = id;
              this.name = name;
              this.sex = sex;
          }
          
          public User(String id, String name, String sex, int age) {
              super();
              this.id = id;
              this.name = name;
              this.sex = sex;
              this.age = age;
          }
          
          public String getId() {
              return id;
          }
          
          public void setId(String id) {
              this.id = id;
          }
          ...
      }

      用戶指定構造方法,沒有指定時則用默認構造方法(沒有則報錯)。
      用戶怎么指定:
      注解 :

      @MapConstructor
      public User(@Arg("id")String id, @Arg("xname")String name,
      @Arg("sex")String sex) {
          super();
          this.id = id;
          this.name = name;
          this.sex = sex;
      }
      /**
      * 標識選用的構造方法
      */
      @Documented
      @Retention(RUNTIME)
      @Target(CONSTRUCTOR)
      public @interface MapConstructor {
      
      }

      xml:根據constructor元素中 arg元素的數量、javaType來確定構造函數。注意arg有順序規則、必須指定構造方法的全部參數。

      <resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User">
          <constructor>
              <arg column="id" javaType="String"/>
              <arg column="name" javaType="String"/>
              <arg column="sex" javaType="String"/>
          </constructor>
      </resultMap>

      2、該給對象哪些屬性值?

      創建出對象后,可以從結果集中取值來填充對象的屬性。
      問題1:該給哪些屬性賦值?
      可以有兩種規則:
      1、用戶指定要給哪些屬性賦值。
      2、自動映射賦值:取列的值賦給同名的屬性。

      兩者可以一起使用。
      那么這里就涉及兩個事情:
      1、用戶如何指定?
      注解方式: 我們給定義一個注解 @Result

      public class User {
          @Result
          private String id;
          @Result(column="xname")
          private String name;
          ...
      }
      @Documented
      @Retention(RUNTIME)
      @Target({ TYPE, FIELD })
      public @interface Result {
          String column() default "";
          
          Class<?> javaType() default void.class;
          
          JdbcType jdbcType() default JdbcType.UNDEFINED;
          
          Class<? extends TypeHandler> typeHandler() default
          UndefinedTypeHandler.class;
      }

      xml方式:

      <resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User">
          <constructor>
              <arg column="id" javaType="String"/>
              <arg column="name" javaType="String"/>
              <arg column="sex" javaType="String"/>
          </constructor>
          
          <result property="age" column="age" />
      </resultMap>

      mybatis-mapper.dtd

      <!ELEMENT resultMap (constructor?,result*)>
      <!ATTLIST resultMap
      id CDATA #REQUIRED
      type CDATA #REQUIRED
      >
      
      <!ELEMENT constructor (arg*)>
      
      <!ELEMENT arg EMPTY>
      <!ATTLIST arg
      javaType CDATA #IMPLIED
      column CDATA #IMPLIED
      jdbcType CDATA #IMPLIED
      typeHandler CDATA #IMPLIED
      name CDATA #IMPLIED
      >
      
      <!ELEMENT result EMPTY>
      <!ATTLIST result
      property CDATA #IMPLIED
      javaType CDATA #IMPLIED
      column CDATA #IMPLIED
      jdbcType CDATA #IMPLIED
      typeHandler CDATA #IMPLIED
      >

      問題:這些信息如何表示、存儲?

      2、是否自動映射如何指定?
      增加一個屬性即可:

      autoMapping="true"

      <resultMap id="User" type="com.study.leesmall.mybatis.sample.model.User" autoMapping="true">
          <result property="age" column="age" />
      </resultMap>
      <!ELEMENT resultMap (constructor?,result*)>
      <!ATTLIST resultMap
      id CDATA #REQUIRED
      type CDATA #REQUIRED
      autoMapping (true|false) #IMPLIED
      >

      注解方式:

      /**
      標識類對象要進行自動映射
      */
      @Documented
      @Retention(RUNTIME)
      @Target({ TYPE, FIELD })
      public @interface AutoMapping {
      
      }
      @AutoMapping
      public class User {
          @Result
          private String id;
          
          @Result(column="xname")
          private String name;
          ...
      }

       

      為方便統一開啟自動映射,我們可以在Configuration中設計一個全局配置參數,具體的可以覆蓋全局的。

       

      在哪可配置它?

       在mybatis-config.xml中增加一個配置項即可。

      <configuration>
          <settings>
              <setting name="autoMappingBehavior" value="PARTIAL"/>
          </settings>
      </configuration>

      3、對象中包含對象該如何映射及處理

      對象中包對象是個問題,先把問題搞清楚,看下面的語句示例:

      <!-- Very Complex Statement -->
      <select id="selectBlogDetails" resultMap="detailedBlogResultMap">
              select
                  B.id as blog_id,
                  B.title as blog_title,
                  B.author_id as blog_author_id,
                  A.id as author_id,
                  A.username as author_username,
                  A.password as author_password,
                  A.email as author_email,
                  A.bio as author_bio,
                  A.favourite_section as author_favourite_section,
                  P.id as post_id,
                  P.blog_id as post_blog_id,
                  P.author_id as post_author_id,
                  P.created_on as post_created_on,
                  P.section as post_section,
                  P.subject as post_subject,
                  P.draft as draft,
                  P.body as post_body
              from Blog B
                  left outer join Author A on B.author_id = A.id
                  left outer join Post P on B.id = P.blog_id
              where B.id = #{id}
      </select>

       再看類

      public class Blog {
          private String id;
          
          private String title;
          
          private Author author;
          
          private List<Post> posts;
          ....
      }
      
      
      public class Author {
          private String id;
          
          private String username;
          ...
      }
      
      
      public class Post {
          private String id;
          
          private String subject;
          ...
      }

      這就是對象中包含對象,要從查詢結果中得到Blog,Blog的Author posts數據。
      這就是ORM中的關系映射問題。
      結果的映射是簡單的,因為就是指定里面的屬性取哪個列的值。

      public class Blog {
          @Result(column="blog_id")
          private String id;
          
          @Result(column="blog_title")
          private String title;
          
          @Result
          private Author author;
          
          @Result
          private List<Post> posts;
          ....
      }
      
      
      public class Author {
          @Result(column="author_id")
          private String id;
          
          @Result(column="author_username")
          private String username;
          ...
      }
      
      public class Post {
          @Result(column="post_id")
          private String id;
          
          @Result(column="post_subject")
          private String subject;
          ...
      }

       我們的ResultMap類也是支持的:

      但是從結果集中取值來填裝對象則是復雜的!
      請先看查詢的結果示例:

       

      while(rs.next()){
      
      }

      復雜點:不是一行一個Blog對象,處理行時要判斷該行的blog是否已取過了。
      問題核心點在哪?
      當我操作一行,如何判斷該行的Blog已經取過沒?
      這就要求要知道區分Blog的唯一標識、區分Author的唯一標識。怎么知道?
      用戶得告訴我們他們的id屬性是哪個,對應的列是哪個。

      讓用戶怎么來指定id屬性呢?
      注解方式:在@Arg 、@Rersult注解中增加id指定項。

      @Documented
      @Retention(RUNTIME)
      @Target(PARAMETER)
      public @interface Arg {
          boolean id() default false;
          
          String name() default "";
          
          String column() default "";
          
          Class<?> javaType() default void.class;
          
          JdbcType jdbcType() default JdbcType.UNDEFINED;
          
          Class<? extends TypeHandler> typeHandler() default
          UndefinedTypeHandler.class;
      }
      @Documented
      @Retention(RUNTIME)
      @Target({ TYPE, FIELD })
      public @interface Result {
          boolean id() default false;
          
          String column() default "";
          
          Class<?> javaType() default void.class;
          
          JdbcType jdbcType() default JdbcType.UNDEFINED;
          
          Class<? extends TypeHandler> typeHandler() default
          UndefinedTypeHandler.class;
      }

      xml方式增加:增加argId、id元素

      <resultMap id="detailedBlogResultMap" type="Blog">
          <constructor>
              <idArg column="blog_id" javaType="int"/>
          </constructor>
          ....
      </resultMap>
      
      
      <resultMap id="AuthorMap" type="Author">
          <id property="id" column="author_id"/>
          <result property="username" column="author_username"/>
          <result property="password" column="author_password"/>
      </resultMap>

       在ResultMap中增加ID信息

       

      問題:要體現出一對一,一對多關系嗎?我們會在哪里需要知道這個關系?

       看一個mybatis中的復雜xml ResultMap示例:

      <!-- 超復雜的 Result Map -->
      <resultMap id="detailedBlogResultMap" type="Blog">
          <constructor>
          <idArg column="blog_id" javaType="int"/>
          </constructor>
          
          <result property="title" column="blog_title"/>
          
          <association property="author" javaType="Author">
              <id property="id" column="author_id"/>
              <result property="username" column="author_username"/>
              <result property="password" column="author_password"/>
              <result property="email" column="author_email"/>
              <result property="bio" column="author_bio"/>
              <result property="favouriteSection" column="author_favourite_section"/>
          </association>
          
          <collection property="posts" ofType="Post">
              <id property="id" column="post_id"/>
              <result property="subject" column="post_subject"/>
              <association property="author" javaType="Author"/>
              <collection property="comments" ofType="Comment">
                  <id property="id" column="comment_id"/>
              </collection>
          </collection>
      </resultMap>

      知道唯一標識了,要判斷前面是否取過了,則還需要有個上下文持有取到的對象,并能根據id列值取到對應的對象。

      為對象類型返回結果定義一個ResultHandler實現ClassTypeResultHandler:

      3 Map

      @Select("select id,name,sex,age,address from t_user where id = #{id}")
      Map queryUser1(String id);

      不能在解析階段獲得ResultMap
      當執行完第一次查詢就可以確定下來

       

      我們從結果集中能得到的是JDBCType

       

      問題:

      1、key 用什么?
        用列名
      2、取成什么java類型的值?
        JDBCType中根據整型類型值獲得對應的JDBCType

      /**
      * Returns the {@code JDBCType} that corresponds to the specified
      * {@code Types} value
      * @param type {@code Types} value
      * @return The {@code JDBCType} constant
      * @throws IllegalArgumentException if this enum type has no
      constant with
      * the specified {@code Types} value
      * @see Types
      */
      public static JDBCType valueOf(int type) {
          for( JDBCType sqlType : JDBCType.class.getEnumConstants()) {
              if(type == sqlType.type)
              return sqlType;
          }
          
          throw new IllegalArgumentException("Type:" + type + " is not a
          valid "+ "Types.java value.");
      }

      TypeHandler ---> javaType
      在TypehandlerRegistry中定義一個JDBCType類型對應的默認的TypeHandler集合,來完成取java值放入到Map中

       

      第一次處理結果時,要把這個ResultMaps填充好,后需查詢結果的處理就是直接使用resultMaps

       5.3.2 方法返回集合

      返回集合就是單個的重復

      if(method.getReturnType() == List.class) {
          Type genericType = method.getGenericReturnType();
          if(genericType == null) {
              // 當集合中放Map
          }
          else if (genericType instanceof ParameterizedType) {
              ParameterizedType t = (ParameterizedType) genericType;
              Class<?> elementType = (Class<?>)t.getActualTypeArguments()[0];
          }
      }

      ----------

      posted @ 2019-03-17 10:58  小不點啊  閱讀(3137)  評論(2)    收藏  舉報
      主站蜘蛛池模板: 欧洲精品码一区二区三区| 国产精品人妻一区二区高 | 国产真人无码作爱免费视频app| 四虎在线中文字幕一区| 午夜免费福利小电影| 亚洲中文字幕在线二页| 国产精品偷乱一区二区三区| 亚洲国产精品久久久天堂麻豆宅男| 国产亚洲精品在av| 国产精品av中文字幕| 麻豆久久久9性大片| 日韩精品一区二区三区中文| 国产精品三级中文字幕| 国产午夜精品无码一区二区| 清纯唯美人妻少妇第一页| 成人白浆一区二区三区在线观看| 日韩有码中文字幕av| 亚洲精品动漫一区二区三| 亚洲av无码精品色午夜蛋壳| 国产精品ⅴ无码大片在线看| 乱人伦人妻中文字幕无码久久网 | 久久99热只有频精品8| 无码专区—va亚洲v天堂麻豆| 国产成人黄色自拍小视频| 久青草视频在线观看免费| 狠狠躁夜夜躁无码中文字幕| 夜夜添狠狠添高潮出水| 波多野结衣av无码| 亚洲国产成人久久精品app| 麻豆成人精品国产免费| 国产精品户外野外| 97无码人妻福利免费公开在线视频 | 日本不卡码一区二区三区| 狠狠躁夜夜躁人人爽天天古典| 男女扒开双腿猛进入爽爽免费看| 欧美成人h精品网站| 亚洲 日本 欧洲 欧美 视频| 久热久视频免费在线观看| 国产丝袜肉丝视频在线| 在线 欧美 中文 亚洲 精品| 夜夜躁狠狠躁日日躁|