天天看點

MyBatis架構:全局配置檔案的屬性,映射檔案,映射檔案的參數問題

全局配置檔案的屬性

properties屬性(基本不用)

<!-- mybatis可以使用properties來引入外部properties配置檔案的内容
    resource:引入類路徑下的資源
    url:引入網絡路徑下的資源,或者磁盤上絕對路徑
   -->
  <properties resource="jdbcconfig.properties"></properties>      
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=1234      

settings設定

這是 MyBatis 中極為重要的調整設定,它們會改變 MyBatis 的運作時行為。

比如裡面的mapUnderscoreToCamelCase就是是否開啟自動駝峰命名規則(camel case)映射,就是從資料庫列名THE_NAME到java屬性名theName的類映射,使用查詢的時候能自動進行比對,注意下劃線後面的字母還是大寫的,這是駝峰命名規則

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>      

typeAliases别名處理器

<!-- 
    typeAliases:别名處理器,可以為我們的Java類型起别名(别名不區分大小寫)
    typeAlias是為某一個Java類型起别名
      type:指定要起别名的類型全限類名,預設是類名的小寫
      alias:指定新的别名
    
    package:為某個包下的所有類批量起别名
        name:指定包名(為目前包以及下面的所有子包的每一個類都起一個預設别名)
    
    但是上面的還有一個問題,就是這個包和它的子包有一個類的名字是一樣的
    這樣的話,mybatis就會報錯,是以我們還有一個方法起别名
    
    使用@Alias注解起别名
    批量起别名的情況下,使用注解為某個類型指定新的别名 
   -->
  <typeAliases>
    <typeAlias type="bean.Emp" alias="emp"/>
    <package name=""/>
  </typeAliases>      

environments屬性

<!-- 
    environments:多種環境,mybatis可以配置多種環境,default是指定使用某種環境,達到快速切換環境的效果
    environment:配置一個具體的環境資訊,id是這個環境的唯一辨別
        transactionManager:事務管理器
          type:事務管理器的類型,有:
              JDBC:JdbcTransactionFactory
              MANAGED:ManagedTransactionFactory
              還可以自定義事務管理器:隻要實作TransactionFactory接口,type是指定它的全限類名就行,
                           或者參考上面兩個這麼寫的就怎麼寫
        
        dataSource:資料源
            type:資料源的類型,有;
                JNDI:JndiDataSourceFactory
                POOLED:PooledDataSourceFactory
                UNPOOLED:UnpooledDataSourceFactory
            還可以自定義資料源:實作DataSourceFactory接口就行,type就是它的全限類名
   -->
  <environments default="development_mysql">
    <environment id="development_mysql">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
      </dataSource>
    </environment>
  </environments>      

databaseIdProvider屬性

全局配置

<!-- 
    databaseIdProvider:讓mybatis支援多資料庫廠商,讓mybatis的移植性更好
    type="DB_VENDOR":作用是得到資料庫廠商的辨別(就是驅動,根據getDatabaseProductName()的方法得到)
    mybatis就能根據資料庫廠商辨別來執行不同的sql
    比如MySQL  Oracle  SQL Server
    可以利用下面這個标簽給資料庫廠商起一個好用的别名
    <property name="" value=""/>
    然後就是映射檔案的配置,
    在select标簽裡面的databaseId="mysql"屬性表明這個查詢是使用哪一個資料庫的
   -->
  <databaseIdProvider type="DB_VENDOR">
    <property name="MySQL" value="mysql"/>
    <property name="Oracle" value="oracl"/>
  </databaseIdProvider>      

映射檔案配置

如果這裡是在mysql環境下,會加載有mysql辨別的和沒有任何辨別的語句,如果執行的時候優先使用帶mysql辨別的sql

<mapper namespace="bean.EmpMapper">
  <select id="getEmpById" resultType="bean.Emp" > 
    select eid,ename name,email,gender from emp where eid= #{id}  
  </select>
  <!-- 這是兩個版本資料庫的查詢,上面是mysql,下面的是oracle的查詢 -->
  <select id="getEmpById" resultType="bean.Emp" databaseId="mysql"> 
    select eid,ename name,email,gender from emp where eid= #{id}  
  </select>
  <select id="getEmpById" resultType="bean.Emp" databaseId="oracle"> 
    select eid,ename name,email,gender from emp where eid= #{id}  
  </select>
</mapper>      

這裡也配置了兩個環境

<environments default="development_mysql">
    <environment id="development_mysql">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
        <property name="driver" value="${jdbc.driver}" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
      </dataSource>
    </environment>
    
    <environment id="development_oracle">
      <transactionManager type="JDBC" />
      <dataSource type="POOLED">
        <property name="driver" value="${orcl.driver}" />
        <property name="url" value="${orcl.url}" />
        <property name="username" value="${orcl.username}" />
        <property name="password" value="${orcl.password}" />
      </dataSource>
    </environment>
    
  </environments>      
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8
jdbc.username=root
jdbc.password=1234

orcl.driver=oracle.jdbc.OracleDriver
orcl.url=jdbc:oracle:thin:@localhost:1521:orcl
orcl.username=scott
orcl.password=1234      

mappers映射注冊

<!-- 将寫好的sql映射檔案注冊到全局配置檔案中 -->
  <!-- 
    mappers:将sql映射注冊到全局檔案中
      mapper:注冊一個sql映射
      注冊配置檔案
          resource:引用類路徑下的sql映射檔案
          url:引用網絡路徑或者磁盤路徑的sql映射檔案
      注冊接口:
        class:引用接口,就是注冊接口,而不是映射檔案了
          1.有sql映射檔案的,映射檔案名必須和接口同名,而且是要放在接口同一目錄下          
          2.mybatis也支援沒有sql映射檔案的,就是所有的sql都是寫在接口的注解上面
          上面兩種方法的推薦:
                  比較重要的,複雜的Dao接口寫在映射檔案上
                  不重要,簡單的Dao接口為了開發友善可以寫在注解上面
          3.第三種就是批量注冊了,使用package标簽
            注意的是批量注冊會找到包下的所有類
            如果是接口注釋的我們還能了解,
            但是如果是檔案注冊的mybatis是怎麼找到映射檔案的呢?
            是以這個批量注冊還是有要求的,如果有映射注冊,
            就必須接口類和映射檔案在同一個包下
            注意你可能覺得java源檔案和xml檔案放在一起,這樣不好看
            是以你可以在資源檔案夾conf的目錄下建立一個和接口類一模一樣的包名
            那麼在編譯的時候,因為src和conf都是資源檔案夾,是以會被解析到一個檔案夾裡面
            那麼這兩個檔案夾就是同一個檔案夾了,隻是在開發的時候視覺上不是同一個檔案夾
   -->
  <mappers>
    <mapper resource="EmpMapper.xml" />
    <mapper class="bean.EmpMapperAnnotation"/>
    <package name="bean"/>
  </mappers>      

​注意那些檔案路徑的問題, 比如什麼java源檔案,批量注冊的包路徑名,包之間使用.(點号), 而什麼xml檔案的路徑名,包之間使用的是/(斜杠)​

映射檔案

增删改查

​​

​下面的代碼中,增删改的操作做,需要傳回值就直接是傳回Integer/Long/Boolean這些類型,這些傳回值mybatis已經定義好了,會自動幫你封裝,隻要傳回不是0行資料,Boolean都是True​

package bean;

public interface EmpMapper
{
  public Emp getEmpById(Integer id);
  public long addEmp(Emp emp);
  public boolean updateEmp(Emp emp);
  public long deleteEmpById(Integer eid);
}      

映射檔案寫法

<!-- 
    public long addEmp(Emp emp);
    public boolean updateEmp(Emp emp);
    public long deleteEmpById(Integer eid);
   -->
   <insert id="addEmp" parameterType="bean.Emp">
      insert into emp (ename,email,gender) values(#{ename},#{email},#{gender})
   </insert>
   
   <update id="updateEmp">
      update emp set ename=#{ename},email=#{email},gender=#{gender}
      where eid=#{eid}
   </update>
   
   <delete id="deleteEmpById">
      delete from emp where eid=#{eid}
   </delete>      
@org.junit.Test
  public void Test4() throws IOException
  {
    String resource="mybatis.xml";
    InputStream resourceAsStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession openSession = sqlSessionFactory.openSession();
    try
    {
      EmpMapper empMapper = openSession.getMapper(EmpMapper.class);
      //Emp emp=new Emp(2, "jane", "jane.com", "男");
      //empMapper.addEmp(emp);
      //boolean updateEmp = empMapper.updateEmp(emp);
      //System.out.println(updateEmp);
      long deleteEmpById = empMapper.deleteEmpById(2);
      System.out.println(deleteEmpById);
      //必須手動送出
      openSession.commit();
    }finally
    {
      openSession.close();
    }
  }      
<insert id="addEmp" parameterType="bean.Emp" useGeneratedKeys="true" keyProperty="eid">
      insert into emp (ename,email,gender) values(#{ename},#{email},#{gender})
   </insert>      
= openSession.getMapper(EmpMapper.class);
      Emp emp=new Emp(null, "jane", "jane.com", "男");
      empMapper.addEmp(emp);
      System.out.println(emp.getEid());      
<!-- 
    擷取非自增的主鍵的值:
      像Oracle是不支援自增的,Oracle使用的是序列來模拟自增的
      每一次插入的資料的主鍵都是從序列中拿到的值,那麼我們如何擷取這個值呢?
      
      mybatis支援使用屬性selectKey來擷取主鍵:
          keyProperty:查出的主鍵值封裝給JavaBean的哪一個屬性
          order:設定目前的selectKey的sql語句在插入sql語句的前後運作,有兩個參數
              BEFORE:目前的selectKey的sql語句在插入sql語句的前運作
                  運作順序:
                      先運作selectKey查詢id的sql;查出id值封裝給javaBean的id屬性
                      在運作插入的sql;就可以取出id屬性對應的值
              AFTER:目前的selectKey的sql語句在插入sql語句的後運作
                  運作順序:
                      先運作插入的sql(從序列中取出新值作為id);
                      再運作selectKey查詢id的sql;
          resultType:查出的資料的傳回值類型
    -->
   <!-- BEFORE的編寫 -->
   <insert id="addEmp" parameterType="bean.Emp"  databaseId="oracle">
      <selectKey keyProperty="eid" order="BEFORE" resultType="Integer">
        select EMPLOYEES_SEQ.nextval from dual 
      </selectKey>
      <!-- 插入時的主鍵時從序列中拿到的 -->
      insert into emp (eid,ename,email,gender) values(#{eid},#{ename},#{email},#{gender})
   </insert>
   
   <!-- AFTER的編寫 -->
   <insert id="addEmp" parameterType="bean.Emp"  databaseId="oracle">
      <selectKey keyProperty="eid" order="AFTER" resultType="Integer">
        select EMPLOYEES_SEQ.currval from dual
      </selectKey>
      <!-- 插入時的主鍵時從序列中拿到的 -->
      insert into emp (eid,ename,email,gender) values(employees_seq.nextval,#{ename},#{email},#{gender})
   </insert>      

映射檔案的參數問題

<!-- 
  mybatis的參數問題:
  單個參數:mybatis不會做特殊的處理
      #{參數名/任意的名稱} :都可以取出參數的值
      
  多個參數:mybatis會做特殊的處理
      多個參數會被封裝成一個map對象,
          key:名稱就是param1,param2...paramN,或者參數的索引也可以取出值
          value:就是你傳入的參數的值
      
      如果直接寫參數名,
      select eid,ename,email,gender from emp where eid= #{eid} and ename=#{ename}
      會報錯:
      Cause: org.apache.ibatis.binding.BindingException: 
      Parameter 'eid' not found. 
      Available parameters are [0, 1, param1, param2]
      我們可以将參數寫成0,1或者param1,param2
      但是如果參數很多,就顯得不合規矩
      是以我們可以命名參數:
        明确指定封裝參數時map的key的值是什麼,
        使用的是@Param注釋進行指明
            key:就是使用@Param的值
            value:參數值
        例如:public Emp getEmpByIdAndName(@Param("eid")Integer eid,@Param("ename")String ename);
  
  上面的命名參數,如果參數實在是不少,那麼寫起來還是挺麻煩的,下面還有三種方法
  POJO:
    如果多個參數正好是我們業務邏輯的資料模型,那麼我們就可以直接傳入pojo,就是JavaBean類
    使用:#{屬性名}進行取出傳入的pojo的屬性值
  Map:
    如果多個參數不是業務邏輯的資料模型,沒有對應的pojo,不經常使用這個sql,為了友善
    我們可以自己建構一個map傳進去
    使用:#{key}就可以取出map對應的值
  TO:
    如果多個參數不是業務模型的資料,但是經常使用,推薦自己編寫一個TO
    (Transfer Object)資料傳輸對象
    
  例子:
      public Emp getEmp(@Param("eid")Integer eid,String ename)
      取值:eid==>#{eid}或#{param1}
        ename==>#{param2}
        
      public Emp getEmp(Integer eid,@Param("e")Emp emp)
      取值:eid==>#{param1}
        ename==>#{param2.ename}或者#{e.ename}
      
      ##注意的是:如果是Collection(List,Set)類型或者是數組
      mybatis也會特殊處理,把傳入的list或者數組封裝在map中
        key:Collection封裝成collection
          如果是List可以使用list
          數組封裝成array
      
      public Emp getEmpById(List<Integer> eids)
      取值:取出第一個id的值:#{list[0]}
      
  結合源碼進行了解:
  參數多時會封裝map,為了不混亂,我們可以使用@Param來指定封裝時使用的key
  使用#{key}就可以取到map中的值
  
  例子:
  (@Param("eid")Integer eid,@Param("ename")String ename);
  ParamNameResolver解析參數封裝map的;
  1. 首先names:{0=eid,1=ename};這是在構造器的時候就建立好了
    怎麼建立的呢:
      1.擷取每一個标注了@Param注解的參數的@Param的值eid,ename,指派給names
      2.每一次解析一個參數給map中儲存資訊
      (key:參數索引,就是第幾個參數,value:name的值
          name的值:标注了@Param注解的:就是注解的值
               沒有标注的:
                    1.全局配置:isUseActualParamName  (jdk1.8):name=參數名
                    2.name=map.size();就是目前元素的索引值
                    比如{0=eid,1=ename,2=2}//如果有第三個元素的話
  2.後面就是對參數args的處理
    假設現在args=[7,"jane"]
    1.如果參數時null就直接傳回
    2.如果隻有一個元素,并且沒有@Param注解,直接傳回args[0]
    3.有多個元素或者有@Param注解
      周遊names集合{0=eid,1=ename,2=2}
      names集合的value作為key,names集合的key又作為取值的參數args[entry.getKey()
      由上面的例子得到:
              {eid=args[0],ename=args[1],2=args[2]}
           結果是:{eid=7,ename="jane",2=2}
    4.最後還額外将每一個參數儲存在map中,使用新的key:param1,param2...paramN
      這樣就可以:有param注解的可以#{key}取值,或者#{param1}取值
  
  public Object getNamedParams(Object[] args) 
  {
      final int paramCount = names.size();
      if (args == null || paramCount == 0) 
      {
        return null;
      }
      else if (!hasParamAnnotation && paramCount == 1) 
      {
        return args[names.firstKey()];
      } 
      else
      {
        final Map<String, Object> param = new ParamMap<Object>();
        int i = 0;
        for (Map.Entry<Integer, String> entry : names.entrySet()) 
        {
          param.put(entry.getValue(), args[entry.getKey()]);
          // add generic param names (param1, param2, ...)
          final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
          // ensure not to overwrite parameter named with @Param
          if (!names.containsValue(genericParamName)) 
          {
            param.put(genericParamName, args[entry.getKey()]);
          }
          i++;
        }
        return param;
    }
  
  //names的源碼  
  private final SortedMap<Integer, String> names;

  private boolean hasParamAnnotation;

  public ParamNameResolver(Configuration config, Method method) 
  {
    final Class<?>[] paramTypes = method.getParameterTypes();
    final Annotation[][] paramAnnotations = method.getParameterAnnotations();
    final SortedMap<Integer, String> map = new TreeMap<Integer, String>();
    int paramCount = paramAnnotations.length;
    // get names from @Param annotations
    for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) 
    {
      if (isSpecialParameter(paramTypes[paramIndex])) 
      {
        // skip special parameters
        continue;
      }
      String name = null;
      for (Annotation annotation : paramAnnotations[paramIndex]) 
      {
        if (annotation instanceof Param) 
        {
          hasParamAnnotation = true;
          name = ((Param) annotation).value();
          break;
        }
      }
      if (name == null) 
      {
        // @Param was not specified.
        if (config.isUseActualParamName()) 
        {
          name = getActualParamName(method, paramIndex);
        }
        if (name == null) 
        {
          // use the parameter index as the name ("0", "1", ...)
          // gcode issue #71
          name = String.valueOf(map.size());
        }
      }
      map.put(paramIndex, name);
    }
    names = Collections.unmodifiableSortedMap(map);
  }
  
   -->      
package bean;

import org.apache.ibatis.annotations.Param;

public interface EmpMapper
{
  public Emp getEmpByIdAndName(@Param("eid")Integer eid,@Param("ename")String ename);
  public Emp getEmpByEmp(Emp emp);
  public Emp getEmpById(Integer id);
  public long addEmp(Emp emp);
  public boolean updateEmp(Emp emp);
  public long deleteEmpById(Integer eid);
}      

參數值的擷取

<!-- 
    參數值的擷取:
      #{}:可以擷取map中的值或者pojo對象的屬性的值
      ${};效果和上面的一樣
    差別是:
      #{}是以預編譯的形式将參數設定到sql語句中的,使用的方法是PreparedStatement,可以防止sql注入
      ${}是将取出的值直接拼接在sql語句中的,會有安全問題
        大多數情況,我們使用的是#{}
        但是原生JDBC不支援占位符的地方,我們就可以使用${}進行取值
        比如:分表,排序,模糊查詢....
          按照年份查詢薪資
          select * from ${year}_salary where xxxx;
          select * from emp orde by ${f_name} ${order}
          select * from emp where ename like '%${thechar}%'
    -->      

#{}的其他用法

<!-- 
      #{}的其他用法:
        規定參數的一些規則:
          javaType、 jdbcType、 mode(存儲過程)、 numericScale、
        resultMap、 typeHandler、 jdbcTypeName、 expression(未來準備支援的功能);
        
        jdbcType通常在某種特定的條件下需要設定:
            就是我們需要插入的資料的某個字段是null值的時候
            有些資料庫可能不能識别mybatis對null值得預設處理,
            比如Oracle就無法識别,插入的時候會報錯,JdbcType OTHER:無效的類型;
            因為mybatis得全局配置中,對于所有的null值都是預設映射原生得jdbc得OTHER類型
            Oracle無法識别這種類型
        解決的方法有:
            原因是全局配置的jdbcTypeForNull=OTHER,
            1.#{email,jdbcType=OTHER}
            2.将全局配置改變:
                <setting name="jdbcTypeForNull" value="NULL"/>
     -->