美文网首页
Mybatis基础实践

Mybatis基础实践

作者: vincent浩哥 | 来源:发表于2019-10-20 20:50 被阅读0次

    Mybatis特点之——动态SQL

    mybatis的动态sql 是基于强大的OGNL表达式来实现的,主要是用来解决 SQL 语句生成的问题。

    动态标签的类型:

    if、choose (when, otherwise)、trim (where, set)、foreach

    if
    需要判断的时候,条件写在 test 中进行使用
    eg:

    <select id="findActiveBlogWithTitleLike" resultType="Blog">
      SELECT * FROM BLOG
      WHERE state = ‘ACTIVE’
      <if test="title != null">
        AND title like #{title}
      </if>
    </select>
    
    <select id="findActiveBlogLike"
         resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <if test="title != null">
        AND title like #{title}
      </if>
      <if test="author != null and author.name != null">
        AND author_name like #{author.name}
      </if>
    </select>
    

    choose (when, otherwise)
    需要选择一个条件的时候使用。
    eg:

    <select id="findActiveBlogLike" resultType="Blog">
      SELECT * FROM BLOG WHERE state = ‘ACTIVE’
      <choose>
        <when test="title != null">
          AND title like #{title}
        </when>
        <when test="author != null and author.name != null">
          AND author_name like #{author.name}
        </when>
        <otherwise>
          AND featured = 1
        </otherwise>
      </choose>
    </select>
    

    trim (where, set)
    需要去掉 where、and、逗号之类的符号的时候使用。
    eg:

    <insert  id" ="insert"  parameterType ="com.vincent.bean.Employee">
        insert into tbl_emp
        <trim  prefix" ="("  suffix" =")"  suffixOverrides =",">
            <if  testd ="empId = !=  null">
                emp_id,
            </if>
            <if  teste ="empName = !=  null">
                emp_name,
            <if>
            <if  testd ="dId = !=  null">
                d_id,
            </if>
        </trim>
        <trim  prefixs ="values " ("  suffix" =")"  suffixOverrides =",">
            <if  testd ="empId = !=  null">
                #{empId,jdbcType=INTEGER},
            </if>
            <if  teste ="empName = !=  null">
                #{empName,jdbcType=VARCHAR},
            </if>
            <if  testd ="dId = !=  null">
                #{dId,jdbcType=INTEGER},
            </if>
        </trim>
    </insert>
    

    foreach
    需要遍历集合的时候使用
    eg:

    <select id="selectPostIn" resultType="domain.blog.Post">
      SELECT *
      FROM POST P
      WHERE ID in
      <foreach item="item" index="index" collection="list"
          open="(" separator="," close=")">
            #{item}
      </foreach>
    </select>
    

    批量操作

    批量插入

    eg:
    Java代码中,直接传入一个List类型参数
    在Mapper文件里面,使用foreach标签拼接values部分语句

    <!-- 批量插入 -->
    <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>
    

    这种做法效率要比循环发送 SQL 执行要高得多。最重要的就在于减少了跟数据库交互的次数,并且避免了开启和结束事务的时间消耗。

    批量更新

    Mapper 文件里面最关键的就是 case when 和 where 的配置。
    eg:

    <update  id ="updateBatch">
        update tbl_emp set emp_name =
        <foreach  collection" ="list"  item" ="emps"  index" ="index"  separator" =" " "  opene ="case  emp_id" close ="end">
            when #{emps.empId} then #{emps.empName}
        </foreach>
            ,gender =
        <foreach  collection" ="list"  item" ="emps"  index" ="index"  separator" =" " "  opene ="case  emp_id" close ="end">
            when #{emps.empId} then #{emps.gender}
        </foreach>
            ,email =
        <foreach  collection" ="list"  item" ="emps"  index" ="index"  separator" =" " "  opene ="case  emp_id" close ="end">
            when #{emps.empId} then #{emps.email}
        </foreach>
            where emp_id in
        <foreach  collection" ="list"  item" ="emps"  index" ="index"  separator" =","  open ="(" close =")">
            #{emps.empId}
        </foreach>
    </update>
    

    batch Executor

    对于批量操作,数据量大的时候,拼接出来的sql语句会过大而报错,
    MySQL 的服务端对于接收的数据包有大小限制,max_allowed_packet 默认是4M,需要修改默认配置才可以解决这个问题。

    在我们的全局配置文件中,可以配置默认的 Executor 的类型。其中有一种BatchExecutor。
    <setting name" ="defaultExecutorType" value" ="BATCH" />

    也可以在创建会话的时候指定执行器类型:
    SqlSession session = sqlSessionFactory.openSession(ExecutorType. BATCH );

    嵌套(关联)查询/ N+1 / 延迟加载

    映射结果的2个标签:resultType、resultMap
    都是属于select标签的一个属性

    用法:
    结果集的列和实体类的属性可以直接映射的用resultType

    返回的字段无法直接映射的用resultMap来建立映射关系

    关联查询2种配置方式(1对1)

    1、 嵌套结果
    eg:

    <!-- 根据文章查询作者,一对一查询的结果,嵌套查询 -->
    <resultMap  id ="BlogWithAuthorResultMap"
    type ="com.vincent.associate.BlogAndAuthor">
        <id  column" ="bid"  property" ="bid"  jdbcType ="INTEGER"/>
        <result  column" ="name"  property" ="name"  jdbcType ="VARCHAR"/>
        <!-- 联合查询,将 author 的属性映射到 ResultMap -->
        <association  property" ="author"  javaType ="com.vincent.domain.Author">
            <id  column" ="author_id"  property ="authorId"/>
            <result  column" ="author_name"  property ="authorName"/>
        </association>
    </resultMap>
    

    2.嵌套查询
    eg:

    <!-- 联合查询 (一对一)的实现,但是这种方式有“N+1”的问题 -->
    <resultMap  id" ="BlogWithAuthorQueryMap"  type ="com.vincent.associate.BlogAndAuthor">
        <id  column" ="bid"  property" ="bid"  jdbcType ="INTEGER"/>
        <result  column" ="name"  property" ="name"  jdbcType ="VARCHAR"/>
        <association  property" ="author"  javaType ="com.vincent.domain.Author"
        column" ="author_id"  select ="selectAuthor"/> <!-- selectAuthor 定义在下面-->
    </ resultMap>
    <!-- 嵌套查询 -->
    <select  id" ="selectAuthor"  parameterType" ="int"  resultType ="com.vincent.domain.Author">
        select author_id authorId, author_name authorName
        from author where author_id = #{authorId}
    </ select>
    

    由于是分2次查询,查询完员工信息之后,会再发送一条sql去查询部门信息,如果只执行了一次查询员工信息的 SQL(所谓的 1),如果返回了 N 条记录,就会再发送 N 条到数据库查询部门信息(所谓的 N),这个就是我们所说的 N+1 的问题。这样会白白地浪费应用和数据库的性能。

    解决办法:利用延迟加载(懒加载)

    在setting 标签里面可以配置:

    <!--延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false -->
    <setting  name" ="lazyLoadingEnabled"  value ="true"/>
    <!--当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过 select 标签的fetchType 来覆盖-->
    <setting  name" ="aggressiveLazyLoading"  value ="false"/>
    <!-- Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认 JAVASSIST -->
    <setting  name" ="proxyFactory"  value" ="CGLIB" />
    

    lazyLoadingEnabled 决定了是否延迟加载。
    aggressiveLazyLoading 决定了是不是对象的所有方法都会触发查询。

    数据源(DataSource)和连接池(Connection Pool)

    数据源就是数据的来源,一个数据源就对应一个数据库。在 Java 里面,它是对数据库连接的一个抽象,一般的数据源都会包括连接池管理的功能

    用连接池的好处
    提升性能,提高资源利用率

    常用的连接池有:
    c3p0:Hibernate 内置的数据库连接池
    dbcp:Apache 项目,Tomcat 自带
    Druid:阿里开源产品,为监控而生
    HikariCP:Springboot 2.0 默认数据库连接池

    Mybatis的连接池:
    MyBatis 内部的连接池,在 settings 里面配置 type="POOLED"就可以了。

    可以自定义连接池,通过实现DataSource接口来自定义

    Druid连接池
    阿里开源的 Druid 是一个号称“为监控而生”的数据源

    1.核心配置

    <bean  id="dataSource"  class ="com.alibaba.druid.pool.DruidDataSource"  init-method ="init"
    destroy-method ="close">
        <!-- 基本属性 url、user、password -->
        <property  name="driverClassName"  value ="${druid.driverClassName}" />
        <property  name="url"  value="${druid.url}" />
        <property  name="username"  value="${druid.username}" />
        <property  name="password"  value="${druid.password}" />
        <!-- 配置初始化大小、最小、最大 -->
        <property  name="maxActive"  value="${druid.maxActive}" />
        <property  name="initialSize"  value="${druid.initialSize}" />
        <property  name="minIdle"  value="${druid.minIdle}" />
        <!-- 配置获取连接等待超时的时间 -->
        <property  name="maxWait"  value="${druid.maxWait}" />
        <!-- 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 -->
        <property name ="timeBetweenEvictionRunsMillis" value="${druid.timeBetweenEvictionRunsMillis}"/>
        <!-- 配置一个连接在池中最小生存的时间,单位是毫秒 -->
        <property  name ="minEvictableIdleTimeMillis" value="${druid.minEvictableIdleTimeMillis}" />
        <property  name="testWhileIdle"  value="${druid.testWhileIdle}" />
        <property  name="testOnBorrow"  value="${druid.testOnBorrow}" />
        <property  name="testOnReturn"  value="${druid.testOnReturn}" />
        <!-- 打开 PSCache,并且指定每个连接上 PSCache 的大小 -->
        <property  name="poolPreparedStatements"value="${druid.poolPreparedStatements}" />
        <property  name="maxOpenPreparedStatements" value ="${druid.maxOpenPreparedStatements}"/>
        <!-- 配置监控统计拦截的 filters -->
        <property  name ="filters"  value="${druid.filters}" />
        <property  name ="connectionProperties" value="config.decrypt=true;config.decrypt.key=${druid.public.key}" />
    </ bean>
    

    2.密码加密配置
    Druid 也提供了加密工具,可以产生一对公钥和私钥。我们用私钥来对密码进行加密,把加密后的密码和私钥配置在配置文件中,应用在使用的时候会自动还原成明文。可以用 jar 包或者工具类的方式对密码进行加密。

    翻页

    逻辑翻页
    逻辑翻页是属于假分页,是把所有数据查出来,然后在内存中删选数据

    物理翻页
    物理翻页是真正的分页,如 MySQL 使用 limit 语句,Oracle 使用 rownum 语句,SQLServer 使用 top 语句。

    翻页插件——PageHelper
    PageHelper 是通过 MyBatis 的拦截器实现的,它会根据 PageHelper 的参数,改写我们的 SQL 语句。如 MySQL会生成 limit 语句,Oracle 会生成 rownum 语句,SQL Server 会生成 top 语句。

    Mybatis-Plus

    MyBatis-Plus是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。

    核心功能:
    通用 CRUD:定义好 Mapper 接口后,只需要继承 BaseMapper<T> 接口即可获得通用的增删改查功能,无需编写任何接口方法与配置文件。

    条件构造器:通过 EntityWrapper<T>(实体包装类),可以用于拼接 SQL 语句,并且支持排序、分组查询等复杂的 SQL。

    代码生成器:支持一系列的策略配置与全局配置,比 MyBatis 的代码生成更好用。

    分页插件

    <!-- spring xml 方式 -->
    <plugins>
        <plugin interceptor="com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor">
            <property name="sqlParser" ref="自定义解析类、可以没有" />
            <property name="dialectClazz" value="自定义方言类、可以没有" />
        </plugin>
    </plugins>
    
    <!--Spring boot方式-->
    @EnableTransactionManagement
    @Configuration
    @MapperScan("com.baomidou.cloud.service.*.mapper*")
    public class MybatisPlusConfig {
        /**
         * 分页插件
         */
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
            // paginationInterceptor.setLimit(你的最大单页限制数量,默认 500 条,小于 0 如 -1 不受限制);
            return paginationInterceptor;
        }
    }
    

    Sequence主键

    相关文章

      网友评论

          本文标题:Mybatis基础实践

          本文链接:https://www.haomeiwen.com/subject/eneguctx.html