• 深入学习Mybatis框架(二)- 进阶


    1.动态SQL

    1.1 什么是动态SQL?

      动态SQL就是通过传入的参数不一样,可以组成不同结构的SQL语句。 这种可以根据参数的条件而改变SQL结构的SQL语句,我们称为动态SQL语句。使用动态SQL可以提高代码重用性。

    1.2 XML方式的实现

      1.2.1 需要使用到的标签

    <if> 用于判断,类似java的if(){}
    <foreach>一般用户批量处理的SQL语句,类似java的foreach循环,
    <trim> :切割标签,主要用于切割关键字的头和尾的字符.新版的Mybatis使用的几率很少.
    <set>:使用 set标签就是SQL语言的set关键字,可以在update 的时候set 关键字后面的,逗号可以自动忽略
    <where>:使用where标签作为SQL语言的where关键字,好处如果where后面的条件都不成立,忽略where关键字.
    <choose> <when> <otherwise> : java的swithc case
    <sql> 用于声明公有的SQL语句块.,在操作标签中使用<include>调用 [一般不建议用]

    不建议的原因,会导致代码难以维护。

      1.2.2 使用示例

      条件查询:(where)

    public List<User> selectByCondition(User user);
    <!-- 条件查询 -->
        <select id="selectByCondition" parameterType="com.gjs.pojo.User" resultType="com.gjs.pojo.User">
            select * from user
            <where>
                <!-- if标签:条件判断标签 -->
                <if test="name!=null">
                    name = #{name}
                <!--或者使用模糊查询: name like concat('%',#{name},'%') -->
                </if>
                <if test="age != null">
                    and age = #{age}
                </if>
            </where>
    </select>

      修改:(set)

    public int updateByNotNull(User user);
    <update id="updateByNotNull" parameterType="com.gjs.pojo.User">
            update user 
            <!-- set -->
            <set>
                <if test="name != null">name=#{name},</if>
                <if test="password != null">password=#{password},</if>
                <if test="age != null">age=#{age}</if>
            </set>
             where id=#{id}
    </update>

      根据条件统计个数(trim )

    public Long selectTotalByCondition(User user);
        <!-- 动态SQL语句trim标签 
                perfix : 动态sql语句的前缀 (WHERE,SET)
                prefixOverrides : 自动截取掉或者替换条(WHERE 多余后面 关键字 :AND-OR)
            -->
        <select id="selectTotalByCondition" parameterType="com.gjs.pojo.User" resultType="long" >
            select count(*) from user
            <trim prefix="WHERE" prefixOverrides="AND|OR">
                <if test="name!=null">
                    name like concat('%',#{name},'%')
                </if>
                <if test="age != null">
                    and age = #{age}
                </if>
            </trim>
        </select>
     <!-- set操作: <trim prefix="SET" suffixOverrides=","> -->

      批量删除:(foreach)

    public int deleteByIds(@Param("ids")List<Integer> ids);
        <delete id="deleteByIds" parameterType="Integer">
            delete from user where id in
            <!-- 动态sql语句 foreach 循环标签 
              <foreach collection="" open="" close="" item="" separator=""></foreach>
                  collection : 要循环集合或者数组 
                  open :开始位置符号 前小括号 (
                  close : 开始位置符号 后小括号 )
                  item : 每次循环的数据
                  separator : 分隔符 逗号 ,
              -->
              <foreach collection="ids" open="(" close=")" item="id" separator=",">
                  #{id}
              </foreach>
        </delete>

    <sql>的使用:

         <sql id="condition_sql">
              <where>
                  <if test="name !=null">
                      <!-- name like '%${name}%' -->
                      name like concat('%',#{name},'%')
                  </if>
                <if test="age !=null">
                    and age = #{age}
                </if>          
              </where>
        </sql>
     
         <select id="selectByCondition" parameterType="com.gjs.pojo.User" resultType="com.gjs.pojo.User">
              select * from user
              <!-- 引入sql片段.  refid :被引入sql片段的id -->
              <include refid="condition_sql_by_trim"/
          </select>

    1.3 注解方式实现

    动态sql除了支持xml方式以外,还是支持使用纯注解的方式
    主要一下四个注解+对应动态sql语句的类文件

    1.@SelectProvider 动态查询SQL语句对应注解
    2.@InsertProvider 动态插入SQL语句对应注解
    3.@UpdateProvider 动态修改SQL语句对应注解
    4.@DeleteProvider 动态删除SQL语句对应注解

      

    示例:
    Usermapper

    package com.gjs.mapper;
    
    import java.util.List;
    
    import org.apache.ibatis.annotations.DeleteProvider;
    import org.apache.ibatis.annotations.Param;
    import org.apache.ibatis.annotations.SelectProvider;
    import org.apache.ibatis.annotations.UpdateProvider;
    
    import com.gjs.pojo.User;
    import com.gjs.pojo.UserProvider;
    
    public interface UserMapper {
    
        
        /*
         * 条件查询
         * type : 编写动态sql语句的类对应的字节码
         * method : 编写动态sql语句类对应的方法名称
         * 此方法返回的是一个String字符串,字符串就是用于注解方法查询的sql语句
         */
        @SelectProvider(type= UserProvider.class,method="selectByCondition")
        public List<User> selectByCondition(User user);
        
        //根据条件统计总数
        @SelectProvider(type= UserProvider.class,method="selectTotalByCondition")
        public Long selectTotalByCondition(User user);
        
         //修改
        @UpdateProvider(type= UserProvider.class,method="updateByNotNull")
        public int updateByNotNull(User user);
        
        //批量删除
        @DeleteProvider(type=UserProvider.class,method="deleteByIds")
        public int deleteByIds(@Param("ids")List<Integer> ids);
    }

      UserProvider
      构建方法参数规则:
        1.非数组、集合的参数,调用方法是什么,构建SQL语句的方法就是什么
        2.是数组、集合的参数,构建的方法需要包一层Map。如:调用方法为:String[] ids ,构建方法格式为Map<String,String[]> ids

    package com.gjs.pojo;
    
    import java.util.List;
    
    import org.apache.ibatis.annotations.Param;
    
    public class UserProvider {
        
        public String selectByCondition(User user) {
            StringBuilder sb = new StringBuilder();
            sb.append("select * from user where 1=1 ");
            if(user.getName()!=null) {
                //由于最后字符串还是返回给Mybatis执行的所有这里需要使用OGNL表达式来获取对象属性的值
                sb.append("and name like concat('%',#{name},'%') ");
            }
            if(user.getAge()!=null) {
                sb.append("and age = #{age}");
            }
            return sb.toString();
        }
        
        public String selectTotalByCondition(User user) {
            StringBuilder sb = new StringBuilder();
            sb.append("select count(1) from user where 1=1 ");
            if(user.getName()!=null) {
                sb.append("and name like concat('%',#{name},'%') ");
            }
            if(user.getAge()!=null) {
                sb.append("and age = #{age}");
            }
            return sb.toString();
        }
        
        public String updateByNotNull(User user) {
            StringBuilder sb = new StringBuilder();
            sb.append("update user set ");
            if(user.getName()!=null) {
                sb.append("name = #{name},");
            }if(user.getPassword()!=null) {
                sb.append("password = #{password},");
            }if(user.getAge()!=null) {
                sb.append("age = #{age},");
            }
            sb.deleteCharAt(sb.length()-1);//删除末尾多余的逗号","
            sb.append("where id = #{id}");
            return sb.toString();
        }
        
        public String deleteByIds(@Param("ids")List<Integer> ids) {
            StringBuilder sb = new StringBuilder();
            sb.append("delete from user where id in(");
            for (int i = 0; i < ids.size(); i++) {
                sb.append("#{ids["+i+"]},");
            }
            sb.deleteCharAt(sb.length()-1);//删除末尾多余的逗号","
            sb.append(")");
            return sb.toString();
        }
    }

    2.缓存

      在Mybatis里面,所谓的缓存就是将已经查询过的记录放在内存的缓冲区或文件上,这样如果再次查询,可以通过配置的策略,命中已经查询过的记录.从而提高查询的效率。
    Mybatis的缓存分为一级缓存和二级缓存

      2.1 一级缓存

       所谓的一级缓存就是会话(SqlSesion对象)级别的缓存,就是同一个会话,如果已经查询过的数据会保存一份在内存中,如果会话没有关闭,再次调用同样的方法查询,不会再查询数据库,而是直接从缓存中取出之前查询的数据。一级缓存默认是打开的,而且是关闭不了的。
      以下几种情况一级缓存会被清空:
        1.关闭会话.close()
        2.进行了操作(增删改),提交了commit();
        3.手工清除缓存clearCache()

      2.2 二级缓存

    二级缓存是 SqlSessionFactory级别,在整个应用都有效,可以在多个会话有效
    MyBatis本身并没有实现二级缓存,二级缓存需要第三方缓存提供商的支持
    Ehcache:
    下载地址:https://github.com/mybatis/ehcache-cache/releases
    学习地址:http://www.mybatis.org/ehcache-cache/

     

    3.MyBatis的对象关系映射

      在实际开发中,一个业务可能涉及到多个数据表的查询,那么多表查询就涉及连接查询(等值连接), 等值连接 表与表之间有一个外键关键。
      但是程序中最终获取的表封装的对象, 对象与对象之间是没有外键关系的,对象和对象之间只有依赖关系

      对象之间关系主要是四种(什么关系应该看从哪个对象的角度)

    一对一 关系
      一个人对应身份证号
    一对多 关系
      一个部门对应多个员工
    多对一 关系
      多个员工对应一个部门
    多对多 关系
      多个学生对应多个老师,多个学生对应多个课程

      MyBatis框架支持多表查询封装对象之间关系:
        <collection>标签: 一对多查询
        <association>标签:多对一和一对一查询
      注:<collection>和<association>为<resultMap>的子标签

    3.1 多对一查询(<association>联合查询标签)(N+1)

      例:以员工为中心来查询关联部门(多对一关系,多个员工对应一个部门)

     数据库表

    员工表
    CREATE TABLE `employee` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(50) DEFAULT NULL,
      `dept_id` int(11) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;
    部门表
    CREATE TABLE `department` (
      `id` int(11) NOT NULL AUTO_INCREMENT,
      `name` varchar(255) DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

    部门类

    package com.gjs.pojo;
    
    public class Department {
        private Integer id;
        private String name;
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Department(Integer id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public Department() {
            super();
        }
        @Override
        public String toString() {
            return "Department [id=" + id + ", name=" + name + "]";
        }
        
    }

    员工类(Employee)

    package com.gjs.pojo;
    
    public class Employee {
        private Integer id;
        private String name;
        
        //以员工为中心来关联部门,多对一关系,多个员工对应一个部门 : many2one
        private Department dept;
        
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public Department getDept() {
            return dept;
        }
        public void setDept(Department dept) {
            this.dept = dept;
        }
        @Override
        public String toString() {
            return "Employee [id=" + id + ", name=" + name + ", dept=" + dept + "]";
        }
    }

    接口

    package com.gjs.mapper;
    
    import com.gjs.pojo.Employee;
    
    public interface Many2OneMapper {
        /**
         * 根据与员工的编码查询出员工对应的所有信息(包含部门)
         * @param id
         * @return
         */
        public Employee selectByEmpId(Integer id);
    }

     接口映射配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 配置映射
    namespace : 命名空间(通俗说法: 给当前映射文件的唯一标识:起一个唯一的名字)    
     -->
    <mapper namespace="com.gjs.mapper.Many2OneMapper">
        
        <!-- 由于查询出来的结果于com.gjs.pojo.Employee中的属性不一致(Department dept),所以不能自动映射 -->
        <select id="selectByEmpId" resultMap="emp_map" parameterType="int">
            select * from employee where id = #{id}
        </select>
        
        <resultMap type="com.gjs.pojo.Employee" id="emp_map">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <!-- 
                问题:private Department dept; 对象如何映射?
                解决方案: 使用联合查询标签
                <association property="" column="" select=""></association>
                property : 需要映射额属性 dept
                column : 已知的部门外检列 dept_id
                select : 调用查询通过部门id查询出对应部门对象的功能的id
                      值规则 : 映射文件的命名空间 + 点儿(.) + 功能id
                      如果是在同一个命名空间,可以省略命名空间和点
             -->
            <association property="dept" column="dept_id" select="selectByDeptId"/>
        </resultMap>
        
        <select id="selectByDeptId" parameterType="int" resultType="com.gjs.pojo.Department">
            select * from department where id = #{id}
        </select>
    </mapper>

    3.2 多对一查询(<collection>集合映射标签)(N+1)

    例:以部门为中心查询部门的所有信息(包括员工),一个部门对应多个员工
    部门类

    package com.gjs.pojo;
    
    import java.util.List;
    
    public class Department {
        private Integer id;
        private String name;
        //以部门为中心查询部门的所有信息(包含员工)
        //一个部门有多个员工 一对多关系:one2many
        // 部门的元员工使用 list集合包装
        private List<Employee> emps;
        
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public List<Employee> getEmps() {
            return emps;
        }
        public void setEmps(List<Employee> emps) {
            this.emps = emps;
        }
        public Department(Integer id, String name) {
            super();
            this.id = id;
            this.name = name;
        }
        public Department() {
            super();
        }
        @Override
        public String toString() {
            return "Department [id=" + id + ", name=" + name + ", emps=" + emps + "]";
        }
        
    }

    员工类

    package com.gjs.pojo;
    
    public class Employee {
        private Integer id;
        private String name;
        
        public Integer getId() {
            return id;
        }
        public void setId(Integer id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "Employee [id=" + id + ", name=" + name + "]";
        }
        
        
    }

    接口

    package com.gjs.mapper;
    
    import com.gjs.pojo.Department;
    
    public interface One2ManyMapper {
        /**
         * 根据与部门的编码查询出部门对应的所有信息(包含所有员工)
         * @param id
         * @return
         */
        public Department selectByDeptId(Integer id);
    }

    接口对应的映射配置文件

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 配置映射
    namespace : 命名空间(通俗说法: 给当前映射文件的唯一标识:起一个唯一的名字)    
     -->
    <mapper namespace="com.gjs.mapper.One2ManyMapper">
        
        <select id="selectByDeptId" parameterType="int" resultMap="dept_map">
            select * from department where id = #{id}
        </select>
        
        <resultMap type="com.gjs.pojo.Department" id="dept_map">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
            <!-- 
            问题 :List<Employee> emps; 集合如何映射?
            解决方案: 使用 <collection>集合映射
            <collection property="" column="" select=""/>
                property :需要映射的属性  emps 对应的list集合
                column : 部门本身的主键 id
                select : 关联查询的功能id
                规则 : 命名空间+点+功能id。 如果同一个命名空间下面直接 功能id即可
             -->
            <collection property="emps" column="id" select="selectEmpsByDeptId"/>
        </resultMap>
        
        <select id="selectEmpsByDeptId" resultType="com.gjs.pojo.Employee" parameterType="int">
            select * from employee where id = #{dept_id}
        </select>
    </mapper>

    3.3 等值连接方式查询

      以上都是用N+1的方式。MyBatis的对象关系映射还有一种等值连接方式。
      以一对多为例:


    pojo类和接口皆与3.2的相同

    映射配置文件:

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
      PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
      "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 配置映射
    namespace : 命名空间(通俗说法: 给当前映射文件的唯一标识:起一个唯一的名字)    
     -->
    <mapper namespace="com.gjs.mapper.One2ManyMapper">
        
        <select id="selectByDeptId" parameterType="int" resultMap="dept_map">
            select e.id e_id ,e.name e_name,d.id d_id,d.name d_name
        from department d JOIN employee e ON d.id = e.dept_id WHERE d.id = #{id};
        </select>
        
        <resultMap type="com.gjs.pojo.Department" id="dept_map">
            <id property="id" column="d_id"/>
            <result property="name" column="d_name"/>
            <!--
                 <collection property="emps" ofType="">
                     在标签内部属性进行手动映射
                 </collection>
                 property : 要映射的属性
                 ofType: 要映射集合泛型的类型 
                 
             -->
            <collection property="emps" ofType="com.gjs.pojo.Employee">
                <id property="id" column="e_id"/>
                <result property="name" column="e_name"/>
            </collection>
        </resultMap>
    </mapper>

    4.MyBatis的逆向工程

      MyBatis的逆向工程能自动帮开发者生成数据库表对应的 pojo实体文件,自动生成映射文件
      自定生成表的各种(CRUD)的sql语句, 但是只能做单表操作,联合查询还得开发者自己编写

      4.1逆向工程的插件安装步骤

        使用逆向工程得先在Eclipse安装逆向工程的插件

       

      

      

       

      判断是否安装成功

      

      4.2 逆向工程创建步骤

        4.2.1.新建一个项目,导入mybatis.jar包和数据库驱动包

        4.2.2 创建生成配置文件

      generatorConfig.xml:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
    <generatorConfiguration>
      <context id="context1">
       <!-- 注释构建 -->
        <commentGenerator>
           <!-- 去掉所有的注释 -->
            <property name="suppressAllComments" value="true"/>
            <property name="suppressDate" value="true"/>
        </commentGenerator>
        
        <!-- 数据库四要素 -->
        <jdbcConnection connectionURL="jdbc:mysql://localhost:3306/mybatis" 
        driverClass="com.mysql.jdbc.Driver" 
        userId="root" 
        password="1234"/>
        <!-- 实体类 : pojo
            targetPackage : 实体类生成后存放的包
            targetProject : 存放的目录一般都放在 src下面
          -->
        <javaModelGenerator targetPackage="com.gjs.pojo" targetProject="mybatis-generator/src" />
        <!-- 映射文件 -->
        <sqlMapGenerator targetPackage="com.gjs.mapper" targetProject="mybatis-generator/src" />
        <!-- 操作接口 
            type 生成映射的形式
                ANNOTATEDMAPPER : 纯注解的,没有xml映射
                XMLMAPPER : 生成的有xml映射文件
        -->
        <javaClientGenerator  targetPackage="com.gjs.mapper" targetProject="mybatis-generator/src" type="XMLMAPPER" />
        
        <!-- 要生成对应表的配置
            tableName : 数据库表名
            //如果下面全部是true,mybatis直接可以使用纯面向对象开发
            enableCountByExample : 是否生成查询总数的 Example 
            enableDeleteByExample : 是否生成删除的 Example 
            enableSelectByExample : 是否生成查询集合的 Example 
            enableUpdateByExample : 是否生成修改的 Example 
         -->
        <table  tableName="user"  enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table>
        <table  tableName="employee" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table>
        <table  tableName="department" enableCountByExample="false" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="false"></table>
      </context>
    </generatorConfiguration>

    主要是数据库四要素、实体类、映射文件、操作接口的配置视情况进行修改

        4.2.3 开始逆向工程

      选中generatorConfig.xml文件右击运行

      

      4.3.逆向功能的缺点

      逆向功能不能逆向多表操作,只能逆向单表操作,多表之间有外键对应java关联关系没办法映射,需要开发者手动编写对应代码。

  • 相关阅读:
    关于素数的具体问题
    Scala Apply
    Scala内部类
    Scala 类和对象
    Scala Tuple类型
    Scala数组
    sql server 游标
    表变量和临时表详解
    子查询详解
    EXEC 和 SP_EXECUTESQL的区别
  • 原文地址:https://www.cnblogs.com/gaojinshun/p/11145826.html
Copyright © 2020-2023  润新知