A股上市公司传智教育(股票代码 003032)旗下技术交流社区北京昌平校区

 找回密码
 加入黑马

QQ登录

只需一步,快速开始

本帖最后由 Mylo 于 2019-7-11 16:33 编辑

MyBatis应用分析与最佳实践A. 传统 JDBC 问题的解决
  • 使用 DataSource 解决资源管理问题
  • DBUtils 提供了各种 ResultSetHandler 帮我们处理结果集

    • 使用 QueryRunner 帮我们实现简化
    • 不用我们不断地调用 rs.getXxx 然后 obj.setXxx
    • 不需要写释放资源代码

  • SpringJDBC

    • 实现 RowMapper 接口,重写 mapRow 方法
    • 转换结果集返回 Object


  • ==JdbcTemplate 和 DBUtils 很像==

    • 但 JdbcTemplate 可能需要自己写 RowMapper
    • 我们可以创建一个通用的 BaseRowMapper<T>

      • 通过反射实现:

        • 名称对应(适配驼峰名命和下划线)
        • 适配字段类型转换




jdbcTemplate.query(sql, new BaseRowMapper(Employee. class));
  • DBUtils 和 SpringJDBC还没解决的问题

    • SQL 语句硬编码
    • 参数只能按顺序传入(占位符)
    • 没有实现实体类到数据库的映射
    • 没有提供缓存功能


B. ORM
  • Hibernate 是一个 ORM 框架

    • 它解决了创建连接,释放资源的重复代码问题
    • 解决了 ResultSet 的映射代码问题
    • 提供了查询缓存
    • 解决了写 SQL 的问题

  • Hibernate 缺点

    • 每次操作都是表的所有字段,无法做到制定部分字段
    • 自动生成的 SQL 很难进行优化
    • 不支持动态 SQL

  • MyBatis

    • 解决了 ResultSet 问题
    • 解决了 SQL 硬编码问题

  • MyBatis 特性

    • 使用连接池对连接进行管理
    • SQL 和代码分离,集中管理
    • 参数映射和动态 SQL
    • 结果集映射
    • 缓存管理
    • 重复 SQL 提取
    • 插件机制

  • 核心对象

    • SqlSessionFactoryBuilder

      • 只要产生了 SqlSessionFactory 就可以销毁

    • SqlSessionFactory(单例)

      • 在整个项目中要一直存在
      • 全局声明周期

    • SqlSession

      • 一次会话

    • Mapper

      • 一次请求



C. MyBatis 配置文件(mybatis-config.xml)
  • 根标签 <configuration>

    • 对应 MyBatis 的 Configuration 类

  • <properties>

    • 读取配置文件

  • <settings>

    • 对 MyBatis 一些核心行为做配置

      • 对日志输出的控制
      • 全局缓存
      • 延迟加载


  • <typeAliases>

    • 设置别名

  • ==<typeHandlers>==

    • Java 类型到 数据库字段类型转换

  • ==<objectFactory>==

    • 把数据库中的字段映射成 java 对象
    • 每次从结果集里获取数据返回对象,默认会调用 DefaultObjectFactory 的 create 方法

  • <plugins>

    • 插件可以拦截 MyBatis 四大对象

      • Ececutor
      • ParameterHandler
      • ResultHandler
      • StatementHandler


  • <environments>

    • 配置数据库环境

  • <mappers>

    • 定位 mapper 映射文件的位置


<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <properties resource="db.properties"/>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <!-- 控制全局缓存(二级缓存)-->
        <setting name="cacheEnabled" value="true"/>
        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
        <setting name="aggressiveLazyLoading" value="false"/>
        <!--  Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
        <setting name="proxyFactory" value="CGLIB" />
        <!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
        <setting name="localCacheScope" value="STATEMENT"/>
        <setting name="localCacheScope" value="SESSION"/>
    </settings>

    <typeAliases>
        <typeAlias alias="blog" type="com.gupaoedu.domain.Blog"/>
    </typeAliases>

    <typeHandlers>
        <typeHandler handler="com.gupaoedu.type.MyTypeHandler"></typeHandler>
    </typeHandlers>

    <!-- 对象工厂 -->
    <objectFactory type="com.gupaoedu.objectfactory.GPObjectFactory">
        <property name="gupao" value="666"/>
    </objectFactory>

    <plugins>
        <plugin interceptor="com.gupaoedu.interceptor.SQLInterceptor">
            <property name="gupao" value="betterme"/>
        </plugin>
        <plugin interceptor="com.gupaoedu.interceptor.MyPageInterceptor">
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
            <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>

    <mappers>
        <mapper resource="BlogMapper.xml"/>
        <mapper resource="BlogMapperExt.xml"/>
    </mappers>
</configuration>
  • 自定义类型转换

public class MyTypeHandler  extends BaseTypeHandler<String> {
    public vo id setNonNullParameter(PreparedStatement ps,  int i, String parameter, JdbcType jdbcType)
        throws SQLException {
        "设置 String 类型的参数的时候调用,Java 类型到 JDBC 类型"
        System. out .println(" " --------------- setNonNullParameter1 :" "+parameter);
        ps.setString(i, parameter);
    }
    public String getNullableResult(ResultSet rs, String columnName)  throws SQLException {
        "根据列名获取 String 类型的参数的时候调用,JDBC 类型到 java 类型"
        System. out .println(" " --------------- getNullableResult1 :" "+columnName);
        return rs.getString(columnName);
    }
    "后面2个方法不用"
}
  • 把自定义类型转换注册到核心配置中

<typeHandlers>
    <typeHandler  handler ="com.gupaoedu.type.MyTypeHandler"></ typeHandler>
</typeHandlers>
  • 使用

<insert id="insertBlog" parameterType="com.gupaoedu.domain.Blog">
    insert into blog (bid, name, author_id)
    values (#{bid,jdbcType=INTEGER},
    #{name,jdbcType=VARCHAR,typeHandler=com.gupaoedu.type.MyTypeHandler},
    #{authorId,jdbcType=INTEGER})
</insert>
  • 返回值的时候,从 JDBC 类型到 Java 类型,在 resultMap 的列上指定 typehandler

<result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.gupaoedu.type.MyTypeHandler"/>D. 动态 SQL 配置<if>
<choose>(<when>,<otherwise>)
<trim>(<where>,<set>)
<foreach><select id ="selectDept" parameterType ="int" resultType ="com.gupaoedu.crud.bean.Department">
    select * from tbl_dept where 1 = 1
    <if test="deptId != null">
        and dept_id = #{deptId, jdbcType=INTEGER}
    </if>
</select>
  • 只需要一个条件

<select id="getEmpList_choose" resultMap="empResultMap" parameterType="com.gupaoedu.crud.bean.Employee">    SELECT * FROM tbl_emp e    <where>        <choose>            <when test="empId !=null">                e.emp_id = #{emp_id, jdbcType=INTEGER}            </when>            <when test="empName != null and empName != ''">                AND e.emp_name LIKE CONCAT(CONCAT('%', #{emp_name, jdbcType=VARCHAR}),'%')            </when>            <when test="email != null ">                AND e.email = #{email, jdbcType=VARCHAR}            </when>            <otherwise>            </otherwise>        </choose>    </where></select>
  • 按条件插入时使用

    • 需要去掉 where、and、逗号之类的符号的时候

  • 注意最后一个条件 dId 多了一个逗号,就是用 trim 去掉的

<update  id ="updateB yPrimaryKeySelective" parameterType ="com.gupaoedu.crud.bean.Employee">    update tbl_emp    <set>        <if test ="empName != null">            emp_name = #{empName,jdbcType=VARCHAR},        </if>        <if test ="gender != null">            gender = #{gender,jdbcType=CHAR},        </if>        <if test ="email != null">            email = #{email,jdbcType=VARCHAR},        </if>        <if test ="dId != null">            d_id = #{dId,jdbcType=INTEGER},        </if>    </set>    where emp_id = #{empId,jdbcType=INTEGER}</update>
  • trim 用来指定或者去掉前缀或者后缀

<insert id ="insertSelective" parameterType="com.gupaoedu.crud.bean.Employee">    insert into tbl_emp    <trim prefix ="("suffix=")" suffixOverrides=",">        <if test="empId != null">            emp_id,        </if>        <if test ="empName != null">            emp_name,        </if>        <if test ="dId != null">            d_id,        </if>    </trim>    <trim prefix="values("suffix=")" suffixOverrides=",">        <if test="empId != null">            #{empId,jdbcType=INTEGER},        </if>        <if test ="empName != null">            #{empName,jdbcType=VARCHAR},        </if>        <if test ="dId != null">            #{dId,jdbcType=INTEGER},        </if>    </trim></insert>E. 批量操作
  • 批量插入 | 批量更新 | 批量删除

    • 如果在 java 代码里写 for 循环批量插入,会不断的创建连接,耗性能
    • 我们使用动态 SQL 标签实现

      • <foreach> 动态生成 SQL 语句

    • 如果数据量特别大,生成的 SQL 就会特别大

      • ==数据库有接收数据包大小限制==

        • max_allowed_packet=4M




<!-- 批量插入 --><insert id="batchInsert" parameterType="java.util.List" useGeneratedKeys="true">    <selectKey resultType="long" keyProperty="id" order="AFTER">        SELECT LAST_INSERT_ID()    </selectKey>    insert into tbl_emp(emp_id, emp_name, gender,email, d_id)     values    <foreach collection="list" item="emps" index="index" separator=",">        (#{emps.empId},#{emps.empName},#{emps.gender},#{emps.email},#{emps.dId})    </foreach></insert>
  • Batch Executor

    • 定义一个批量操作的执行器
    • 在 <settings> 标签里设置

      • <defalutExecutorType>

      • SIMPLE(默认)
      • REUSE
      • BATCH(批处理)

    • 也可以在创建会话的时候指定


SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);F. 嵌套(关联)查询 / N + 1 / 延时加载
  • 对象关联时有 2 种方式

  • 嵌套结果

<select id="selectBlogWithAuthorResult" resultMap="BlogWithAuthorResultMap">    select b.bid, b.name, b.author_id, a.author_id , a.author_name    from blog b    left join author a    on b.author_id=a.author_id    where b.bid = #{bid, jdbcType=INTEGER}</select> <resultMap id="BlogWithAuthorResultMap" type="com.gupaoedu.domain.associate.BlogAndAuthor">    <id column="bid" property="bid" jdbcType="INTEGER"/>    <result column="name" property="name" jdbcType="VARCHAR"/>    <!-- 联合查询,将author的属性映射到ResultMap -->    <association property="author" javaType="com.gupaoedu.domain.Author">        <id column="author_id" property="authorId"/>        <result column="author_name" property="authorName"/>    </association></resultMap>
  • 嵌套查询

    • 当调用 getAuthor 的时候就会触发 selectAuthor 查询
    • 这样子会出现 N + 1 的问题

      • 1 是查询当前对象
      • N 是该对象关联的查询
      • ==使用延迟加载解决==

        • 在全局配置中配置延迟加载(默认是关闭的)




<select id="selectBlogWithAuthorQuery" resultMap="BlogWithAuthorQueryMap">    select b.bid, b.name, b.author_id, a.author_id , a.author_name    from blog b    left join author a    on b.author_id=a.author_id    where b.bid = #{bid, jdbcType=INTEGER}</select><!-- 另一种联合查询(一对一)的实现,但是这种方式有“N+1”的问题 --><resultMap id="BlogWithAuthorQueryMap" type="com.gupaoedu.domain.associate.BlogAndAuthor">    <id column="bid" property="bid" jdbcType="INTEGER"/>    <result column="name" property="name" jdbcType="VARCHAR"/>    <association property="author" javaType="com.gupaoedu.domain.Author" column="author_id" select="selectAuthor"/></resultMap><select id="selectAuthor" parameterType="int" resultType="com.gupaoedu.domain.Author">    select author_id authorId, author_name authorName    from author where author_id = #{authorId}</select><!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false --><setting name="lazyLoadingEnabled" value="true"/><!--当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过 select 标签的 fetchType 来覆盖--><setting name="aggressiveLazyLoading" value="false"/><!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认 JAVASSIST --><setting name="proxyFactory" value="CGLIB"/>G. 翻页
  • 逻辑翻页与物理翻页的区别

    • 逻辑翻页:假的翻页,把所有的数据都加载到内存,然后把需要返回
    • 物理翻页:真正的翻页

  • 逻辑翻页的支持:RowBounds

    • 数据量大的时候很影响性能

  • 物理翻页的几种实现方式

    • 直接在 Mapper 的 SQL 代码里写 limit,然后传入参数
    • 使用 PageHelper

      • 帮我们自动生成分页代码
      • 使用的是 MyBatis 插件



H. 配置太多
  • MyBatis 只是帮我们管理了数据源,创建连接,释放资源,动态 SQL
  • 但是还是需要写很多 SQL,包括接口映射等

    • ==使用逆向工程可以解决==
    • MyBatis 官方提供了这个工程 mybatis-generator,运行 main 方法就会生成文件


I. 通用 Mapper
  • ==主要解决单表的增删改查问题,并不适用于多表关联查询的场景==
  • MyBatis 支持继承,Mapper 只能继承 SQL 语句的标签
  • 如果字段发生变化了

    • 使用继承

      • 自己的类继承动态生成的类
      • 这样就不用改自己的 mapper.xml
      • ==但是每一张表都多了 2 个 Mapper 类和 2 个映射文件==

    • ==使用通用 Mapper==

      • 继承一个通用的类,自动帮你写好通用的逻辑



J. MyBatis-Plus
  • 通用 crud
  • 条件构造
  • 代码生成
  • 翻页
  • 批处理


0 个回复

您需要登录后才可以回帖 登录 | 加入黑马