天天看点

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