MyBatis进阶

MyBatis进阶

MyBatis进阶

1.上节回顾

  1. 说下你对MyBatis的理解
  2. 使用MyBatis的基本步骤
  3. 使用动态代理注意事项,
  4. 动态代理的实现机制
  5. #{}的作用

2.常见面试题

  1. 当实体类中的属性名和表中的字段名不一样 ,怎么办 ?

  2. #{}和${}的区别是什么?

  3. 如何获取自动生成的(主)键值?

  4. 在mapper中如何传递多个参数?

  5. 如何实现1对N,N对1的查询

  6. MyBatis动态SQL执行原理

    通常一个Xml映射文件,都会写一个Dao接口与之对应,请问,这个Dao接口的工作原理是什么?Dao接口里的方法,参数不同时,方法能重载吗?

    Dao接口的工作原理是JDK动态代理,Mybatis运行时会使用JDK动态代理为Dao接口生成代理proxy对象,代理对象proxy会拦截接口方法,转而执行MappedStatement所代表的sql,然后将sql执行结果返回。

3.本节重点

  • 全局配置文件讲解
    • properties
    • settings
    • typeAliases
    • environments
    • mapper
  • Mapper映射文件配置讲解
    • Mapper配置输入映射
    • Mapper配置输出映射
      • 关联的嵌套结果映射 N-1
      • 集合的嵌套结果映射 1-N
    • Mapper配置动态SQL语句

使用注解实现

​ 使用注解来映射简单语句会使代码显得更加简洁,然而对于稍微复杂一点的语句,Java 注解就力不从心了,并且会显得更加混乱。 因此,如果你需要完成很复杂的事情,那么最好使用 XML 来映射语句。

​ 选择何种方式来配置映射,以及认为映射语句定义的一致性是否重要,这些完全取决于你和你的团队。 换句话说,永远不要拘泥于一种方式,你可以很轻松的在基于注解和 XML 的语句映射方式间自由移植和切换。

使用注解实现查询:

 @Select("select * from blog where id=#{id}")
    Blog queryBlog(int id);

注意使用注解的话,SQL映射文件中就不用再写SQL配置了

    <!--<select id="queryBlog" parameterType="int" resultType="blog">
        select * from blog where id=#{id}
    </select>-->

4.内容详情

4.1 全局配置文件详解

下面元素顺序不要更改,properties必须出现在首行

1571023003044

4.1.1 properties(属性)

​ 这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递

​ 数据库信息经常变动(属性及不同的库)避免数据库信息的硬编码,而且过于集中的配置不利于维护,所以应单独为DB配置一个properties文件。

如果属性在不只一个地方进行了配置,那么 MyBatis 将按照下面的顺序来加载:

  • 在 properties 元素体内指定的属性首先被读取。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件或根据 url 属性指定的路径读取属性文件,并覆盖已读取的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖已读取的同名属性。

4.1.2 setting

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。

<settings>
    <!--是否开启缓存-->
  <setting name="cacheEnabled" value="true"/>
     <!--是否开启延迟加载-->
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
     <!--设置的是自动映射级别-->
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
    <!--是否开启自动驼峰命名规则映射-->
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
     <!--延迟加载指定的方法-->
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
   <!--所用日志的具体实现--> 
    <setting name="logImpl" value="LOG4J"/>
</settings>
  • 自动映射(autoMappingBehavior)

​ 当自动映射查询结果时,MyBatis 会获取结果中返回的列名并在 Java 类中查找相同名字的属性(忽略大小写)。 这意味着如果发现了 ID 列和 id 属性,MyBatis 会将列 ID 的值赋给 id 属性。

​ 通常数据库列使用大写字母组成的单词命名,单词间用下划线分隔;而 Java 属性一般遵循驼峰命名法约定。为了在这两种命名方式之间启用自动映射,需要将 mapUnderscoreToCamelCase 设置为 true。

有三种自动映射等级

  • NONE - 禁用自动映射。仅对手动映射的属性进行映射。
  • PARTIAL - 对除在内部定义了嵌套结果映射(也就是连接的属性)以外的属性进行映射
  • FULL - 自动映射所有属性。

在该结果映射中,BlogAuthor 均将被自动映射。但是注意 Author 有一个 id 属性,在 ResultSet 中也有一个名为 id 的列,所以 Author 的 id 将填入 Blog 的 id,这可不是你期望的行为。 所以,要谨慎使用 FULL

4.1.3 typeAliases(类型别名)

​ 类型别名是为 Java 类型设置一个短的名字。 它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余

    <typeAliases>
<!--        <typeAlias type="com.aaa.entity.Blog" alias="blog"></typeAlias>-->
       <package name="com.aaa.entity"/> 
    </typeAliases>

如果使用的是 每一个在包 com.aaa.blog 中的 Java Bean,在没有注解的情况下,会使用Bean 的首字母小写的非限定类名来作为它的别名。比如 com.aaa.Blog的别名为blog;若有注解,则别名为其注解值。见下面的例子:

@Alias("test")
public class Blog {
}

4.1.4 environments(环境配置)

​ MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中, 现实情况下有多种理由需要这么做。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。有许多类似的使用场景。

<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/>
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>

不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

每个数据库对应一个 SqlSessionFactory 实例

注意这里的关键点:

  • 默认使用的环境 ID(比如:default="development")。
  • 每个 environment 元素定义的环境 ID(比如:id="development")。
  • 事务管理器的配置(比如:type="JDBC")。
  • 数据源的配置(比如:type="POOLED")。

默认的环境和环境 ID 是自解释的,因此一目了然。 你可以对环境随意命名,但一定要保证默认的环境 ID 要匹配其中一个环境 ID。

4.1.4.1 transactionManager(事务管理器)

​ 在 MyBatis 中有两种类型的事务管理器(也就是 type=”[JDBC|MANAGED]”):

  • JDBC – 这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域。
  • MANAGED – 这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。
4.1.4.2 dataSource(数据源)

dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

  • 许多 MyBatis 的应用程序会按示例中的例子来配置数据源。虽然这是可选的,但为了使用延迟加载,数据源是必须配置的。

有三种内建的数据源类型(也就是 type=”[UNPOOLED|POOLED|JNDI]”)

UNPOOLED– 这个数据源的实现只是每次被请求时打开和关闭连接。虽然有点慢,但对于在数据库连接可用性方面没有太高要求的简单应用程序来说,是一个很好的选择。
不同的数据库在性能方面的表现也是不一样的,对于某些数据库来说,使用连接池并不重要,这个配置就很适合这种情形。

POOLED– 这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 这是一种使得并发 Web 应用快速响应请求的流行处理方式。

4.1.5 mappers配置分析

MyBatis是基于SQL映射配置的框架,SQL语句都写在Mapper配置文件中,当构建SqlSession之后是需要读取Mapper配置文件中的SQL配置的,mappers标签就是用来配置需要加载的SQL映射配置。

告诉 MyBatis 到哪里去找映射文件

    <mappers>
<!--        <mapper resource="com/aaa/mapper/BlogMapper.xml"></mapper>-->
<!--        <mapper url="file:///workspace/Mybatis/mybatisf01/src/main/java/com/aaa/mapper/BlogMapper.xml"></mapper>-->
<!--        <mapper class="com.aaa.mapper.BlogMapper"></mapper>-->
        <package name="com.aaa.mapper"/>
    </mappers>

​ 这些配置会告诉了 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了,也就是接下来我们要讨论的。

4.2 SQL映射文件配置讲解

4.2.1 select,insert,update,delete元素的常用属性

  1. select常用属性
 id="selectPerson"
 parameterType="int"
 parameterMap="deprecated"
 resultType="hashmap"
 resultMap="personResultMap"
 flushCache="false"
 useCache="true"
 timeout="10"
 fetchSize="256"
 statementType="PREPARED"
 resultSetType="FORWARD_ONLY">

  1. insert,update,delete常用属性

    返回自增主键的问题

    • 添加设置useGeneratedKeys=“true" ,添加keyProperty属性值,对应的是实体类中用来接收主键值的属性名
    <insert id="addUser" parameterType="com.aaa.entity.User" useGeneratedKeys="true" keyProperty="userId">
            insert into user(user_name,password,telephone) values (#{userName},#{password},#{telephone})
        </insert>
    
    • 使用selectKey

      <insert id="addUser" parameterType="com.aaa.entity.User">
              <selectKey keyProperty="userId" order="AFTER" resultType="int">
                  select LAST_INSERT_ID()
              </selectKey>
              insert into user(user_name,password,telephone) values (#{userName},#{password},#{telephone})
          </insert>
      

4.2.2 SQL(SQL语句块)

这个元素可以被用来定义可重用的 SQL 代码段,这些 SQL
代码可以被包含在其他语句中。

   <sql id="blogSql">
        blog_id,blog_name,blog_content
    </sql>
    <select id="queryBlog"  resultType="blog">
        select <include refid="blogSql"></include> from blog where blog_id=#{blogId}
    </select>

4.2.3 Mapper配置输入映射

​ 参数类型可以是原始类型或简单数据类型(比如 IntegerString)或者是简单数据类型的包装类,或者是自定义的JavaBean

如何实现传递多个参数:

  1. 单独传递

    需要指定参数

    mappe接口代码:

    void editUserOnly(@Param("userId") int userId, @Param("userName") String userName);
    

    sql映射文件代码:

    <update id="editUserOnly">
            update user u set u.user_name=#{userName} where user_id=#{userId}
        </update>
    
  2. 封装成实体

    #{} 告诉MyBatis创建一个预处理参数。接收输入参数的类型可以是简单类型,普通JavaBean或者HashMap,如果接收的是JavaBean,会通过OGNL读取对象中的属性值,例如本例中User的userName,userId属性。

     <update id="editUser" parameterType="com.aaa.entity.User">
            update user u set u.user_name=#{userName} where user_id=#{userId}
        </update>
    

    mapp接口代码:

    void editUser(User user);
    
  3. 封装成map

    • 修改操作

      sql映射文件配置:

       <update id="editUserMap" parameterType="map">
              update user u set u.user_name=#{userName} where user_id=#{userId}
          </update>
      

      mapper接口代码:

       void editUserMap(Map userMap);
      

4.2.4 Mapper配置输出映射

4.2.4.1 resultType

​ 一般情况下,resultType输出类型是一种Java基础类型或者包装类型,或自定义的JavaBean,并且从数据库取出的字段名和属性对应,无需任何改动。

返回一个集合的情况:注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。可以使用resultType 或 resultMap,但不能同时使用。

#{}与${}的区别

  1. #{} 告诉MyBatis创建一个预处理参数,在预编译的时候当作?处理;避免了SQL注入

  2. ${} 将数据以字符串的形式原封不动的传入SQL命令中,一般在用到列名,表明的时候使用;

      <select id="queryBlogAll"  resultType="blog">
            select * from blog order by ${blogId}
        </select>
    
4.2.4.2 resultMap

​ resultMap元素是 MyBatis 中最重要最强大的元素。resultMap解决返回的结果没有匹配的封装类接受,或者我们只是想通过一个容器接受返回的结果,提供给业务层来进行处理。

<resultMap id="blogMap" type="blog">
    <!--下面的id和result元素都将一个列的值映射到一个简单数据类型(String, int, double, Date
等)的属性或字段。
id – 结果的唯一标志,id元素表示的结果将是对象的标识属性,这会在比较对象实例时用到。
这样可以提高整体的性能,尤其是进行缓存和嵌套结果映射(也就是连接映射)的时候。
result:result标签是对普通列的定义-->
        <id property="blogId" column="key"></id>
        <result property="blogName" column="blog_name"/>
        <result property="blogContent" column="blog_content"/>
    </resultMap>

resultMap:

  • id:当前命名空间中的一个唯一标识,用于标识一个结果映射
  • type:类的完全限定名, 或者一个类型别名
4.2.2.2.1 关联的嵌套结果(N对1的关系)

关联(association)元素处理“有一个”类型的关系。 比如,在我们的示例中,一个博客有一个作者。

关联的不同之处是,你需要告诉 MyBatis 如何加载关联。MyBatis 有两种不同的方式加载关联:

  • 嵌套 Select 查询:通过执行另外一个 SQL 映射语句来加载期望的复杂类型。
  • 嵌套结果映射:使用嵌套的结果映射来处理连接结果的重复子集。

关联的嵌套select查询

  1. 创建实体类Auth(authId,authName,authEmail),新建表格auth(auth_id,auth_name,auth_email)
  2. sql映射文件的配置:

注意 association 中column的值是blog表的外键字段名

 <select id="queryBlogSelect"  resultMap="blogMapSelect">
        select * from blog where blog_id=#{blogId}
    </select>

    <resultMap id="blogMapSelect" type="blog">
        <id property="blogId" column="blog_id"></id>
        <result property="blogName" column="blog_name"/>
        <result property="blogContent" column="blog_content"/>
        <!--注意 association 中column的值是blog表的外键字段名-->
        <association property="author" column="blog_auth_id" javaType="author" select="queryBlogAuthor">
        </association>
    </resultMap>

    <select id="queryBlogAuthor" resultType="author">
        select * from author where auth_id=#{value}
    </select>
  • 上述这种方式虽然很简单,但在大型数据集或大型数据表上表现不佳。这个问题被称为“N+1 查询问题”。概括地讲,N+1 查询问题是这样子的:
    • 你执行了一个单独的 SQL 语句来获取结果的一个列表(就是“+1”)。
    • 对列表返回的每条记录,你执行一个 select 查询语句来为每条记录加载详细信息(就是“N”)。

​ 这个问题会导致成百上千的 SQL 语句被执行。有时候,我们不希望产生这样的后果。

​ 好消息是,MyBatis 能够对这样的查询进行延迟加载,因此可以将大量语句同时运行的开销分散开来。 然而,如果你加载记录列表之后立刻就遍历列表以获取嵌套的数据,就会触发所有的延迟加载查询,性能可能会变得很糟糕。

​ 所以还有另外一种方法。

关联的嵌套结果映射:

​ mapper映射文件配置:

​ 下面的select 语句要关联2张表格

 <select id="queryBlog"  resultMap="blogMap">
        select b.*,a.* from blog b left join author a on b.blog_auth_id=a.auth_id where b.blog_id=#{blogId}
    </select>

    <resultMap id="blogMap" type="blog">
        <id property="blogId" column="blog_id"></id>
        <result property="blogName" column="blog_name"/>
        <result property="blogContent" column="blog_content"/>
        <association property="author" column="blog_auth_id" javaType="author" resultMap="authMap">
        </association>
    </resultMap>

    <resultMap id="authMap" type="author">
        <id property="authId" column="auth_id"/>
        <result property="authName" column="auth_name"/>
        <result property="authEmail" column="auth_email"/>
    </resultMap>

非常重要
id 元素在嵌套结果映射中扮演着非常重要的角色。你应该总是指定一个或多个可以唯一标识结果的属性。虽然即使不指定这个属性,MyBatis 仍然可以工作,但是会产生严重的性能问题。只需要指定可以唯一标识结果的最少属性。显然,你可以选择主键(复合主键也可以)。

4.2.2.2.2集合的嵌套结果

​ 集合(collection )元素处理“有多个”类型的关系。 比如,在我们的示例中,一篇博客会有很多评论信息。集合元素和关联元素的配置几乎是一样的

  1. 集合的嵌套select查询

    • 创建实体类Comment(commId,commContent,commDate(java.util.Date)),新建表格comment(comm_id,comm_content,comm_date(timestamp))

    • sql映射文件的配置:

      基本与association很类似,替换了javaType为ofType

      注意:column的值是blog表的主键字段名

     <select id="queryBlogCollS"  resultMap="blogMapCollS">
            select * from blog where blog_id=#{blogId}
        </select>
    
        <resultMap id="blogMapCollS" type="blog">
            <id property="blogId" column="blog_id"></id>
            <result property="blogName" column="blog_name"/>
            <result property="blogContent" column="blog_content"/>
            <!--注意:column的值是blog表的主键字段名-->
            <collection property="commentList" column="blog_id" ofType="comment" select="commentSelect">
            </collection>
        </resultMap>
    
        <select id="commentSelect" resultType="comment">
            select * from comment where comm_blog_id=#{value}
        </select>
    
  2. 集合的嵌套结果映射

    select 语句要关联2张表格

     <!--
        集合的嵌套的结果映射
    -->
        <select id="queryBlogColl"  resultMap="blogMapColl">
            select b.*,c.* from comment c left join blog b on b.blog_id=c.comm_blog_id where b.blog_id=#{blogId}
        </select>
    
        <resultMap id="blogMapColl" type="blog">
            <id property="blogId" column="blog_id"></id>
            <result property="blogName" column="blog_name"/>
            <result property="blogContent" column="blog_content"/>
            <collection property="commentList" column="blog_id" ofType="comment" resultMap="commentMap">
            </collection>
        </resultMap>
    
        <resultMap id="commentMap" type="comment">
            <id property="commId" column="comm_id"/>
            <result property="commContent" column="comm_content"/>
            <result property="commDate" column="comm_date"/>
        </resultMap>
    

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://championmi.cn/archives/mybatis进阶

Buy me a cup of coffee ☕.