• MyBatis框架——动态SQL、缓存机制、逆向工程


    MyBatis框架——动态SQL、缓存机制、逆向工程

    一、Dynamic SQL

    为什么需要动态SQL?有时候需要根据实际传入的参数来动态的拼接SQL语句。
    最常用的就是:where和if标签

    1.参考官方文档

    if:字符判断
    choose (when, otherwise):分支选择
    trim (where, set):字符串截取;其中where标签封装查询条件,set标签封装修改条件
    foreach:遍历,实现批处理

    2.if案例:

    1)在EmployeeMapper接口中添加一个方法:
    //携带了哪个字段,查询条件就带上哪个字段的值
    public List<Employee> getEmployeeByConditionIf(Employee employee);
    

      

    2).如果要写下列的SQL语句,只要是不为空,就作为查询条件,如下所示,这样写实际上是有问题的,所以我们要写成动态SQL语句:

    <select id="getEmployeeByConditionIf" resultType="com.neuedu.entity.Employee">
      select *from tbl_employee where id = #{id} and user_name = #{userName} and email = #{email} and gender = #{gender} 
    </select>
    

      

    3)用if标签改写为动态SQL,如下所示:

    <select id="getEmployeeByConditionIf" resultType="com.neuedu.entity.Employee">
      select *from tbl_employee 
      where 
      <!--
        test:判断表达式(OGNL)
        OGNL参照PPT或者官方文档。
        c:if test
        从参数中取值进行判断
        遇见特殊符号,应该去写转义字符:参考W3CSchool>>HTML>>ISO8859
      -->
      <if test="id != null">
        id = #{id}
      </if>
      <if test="userName != null && userName !=''">
        and user_name = #{userName} 
      </if>
      <if test="email != null and email.trim() != """>
        and email = #{email}
      </if>
      <!-- ognl会进行字符串和数字的转换判断;"0"==0,"1"==1 -->
      <if test="gender == 0 or gender == 1">
        and gender = #{gender} 
      </if>
    </select>
    

      

    4).测试代码:

    @Test
    public void testGetEmployee(){
      EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
      Employee employee = new Employee();
      employee.setId(1);
      employee.setUserName("张三丰");
      employee.setEmail("sunwukong@163.com");
      employee.setGender(1);
      List<Employee> list = mapper.getEmployeeByConditionIf(employee);
      System.out.println(list);
    }

    测试结果没问题 。

    但是仔细来说,上面的sql语句是有问题的,当我们不给动态sql语句传递id值的时候,sql语句的拼装就会有问题!

    解决办法:

    1.给where后面加上1=1,以后的条件都可以使用and xxx了
    2.mybatis可以使用where标签来将所有的查询条件包括在内。mybatis就会将where标签中拼装的sql,多出来的and或者or去掉!

     

    //需要注意:where标签只会去掉第一个多出来的and或者or

    3.也就是说使用where标签有时候还是不能解决问题的,那怎么办呢?我们这里可以使用trim标签!

    2.trim标签:可以自定义字符串的截取规则

    <select id="getEmployeeByConditionIf" resultType="com.neuedu.entity.Employee">
      select *from tbl_employee
      <!-- 
        后面多出的and或者or where标签不能够解决
        prefix="":前缀:trim标签体重是整个字符串拼串后的结果。
        prefix给拼串后的整个字符串加一个前缀
        prefixOverrides="":
        前缀覆盖:去掉整个字符串前面多余的字符
        suffix="":后缀
        suffix给拼串后的整个字符串加一个后缀
        suffixOverrides="":
        后缀覆盖:去掉整个字符串后面多余的字符
      --> 
      <trim prefix="where" suffixOverrides="and">
        <if test="id != null">
          id = #{id} and
        </if>
        <if test="userName != null && userName !=''">
          user_name = #{userName} and 
        </if>
        <if test="email != null and email.trim() != """>
          email = #{email} and 
        </if>
        <!-- ognl会进行字符串和数字的转换判断;"0"==0,"1"==1 -->
        <if test="gender==0 or gender==1">
          gender = #{gender}
        </if>	
      </trim>
    </select>
    

      

    3.choose标签:分支选择,类似于Java中的带了break的switch...case

    choose (when, otherwise):如果带了id,就用id查,如果带了userName就用userName查,只会进入其中一个!

    案例演示:

    1).在EmployeeMapper接口中添加一个方法:

    public List<Employee> getEmployeeByConditionChoose(Employee employee);


    2).sql映射文件

    <!-- public List<Employee> getEmployeeByConditionChoose(Employee employee); -->
    <select id="getEmployeeByConditionChoose" resultType="com.neuedu.entity.Employee">
      select *from tbl_employee
      <where>
        <!-- 如果带了id,就用id查,如果带了userName就用userName查,只会进入其中一个! -->
        <choose>
        <when test="id != null">
          id = #{id}
        </when>
        <when test="userName != null">
          user_name like #{userName}
        </when>
        <when test="email != null">
          email = #{email}
        </when>
        <otherwise>
          1=1
        </otherwise>
        </choose>
      </where>
    </select>
    

      

    4.trim 中的set标签(where, set):字符串截取;其中where标签封装查询条件,set标签封装修改条件

    set元素会动态前置set关键字,同时也会消除无关的逗号。

    1).在EmployeeMapper中添加一个更新的方法,如下所示:

    public void updateEmp(Employee employee);
    

      

    2).在sql映射文件中,填写相应的sql语句,如下所示【set标签可以将字段后面的逗号去掉】:

    <update id="updateEmp">
      update tbl_employee 
      <set>
        <if test="userName != null">
          user_name = #{userName},
        </if>
        <if test="email != null">
          email = #{email},
        </if>
        <if test="gender != null">
          gender = #{gender},
        </if>
      </set>
      where id = #{id}
    </update>
    

      

    3).测试类代码为:

    @Test
    public void testGetEmployee(){
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Employee employee = new Employee();
    employee.setId(1);
    employee.setUserName("哈哈");
    employee.setEmail("sunwukong@163.com");
    employee.setGender(1);
    mapper.updateEmp(employee);
    }
    

     

    4).当然上面的set标签我们也可以使用trim标签来代替,如下所示:

    <update id="updateEmp">
    update tbl_employee 
    <trim prefix="set" suffixOverrides=",">
    <if test="userName != null">
    user_name = #{userName},
    </if>
    <if test="email != null">
    email = #{email},
    </if>
    <if test="gender != null">
    gender = #{gender},
    </if>
    </trim>
    where id = #{id}
    </update>
    

      

    5.foreach:遍历元素

    动态SQL的另一个常用的操作是需要对一个集合进行遍历,通常在构建in条件语句的时候!
    foreach元素允许指定一个集合,声明集合项和索引变量,并可以指定开闭匹配的字符串以及在迭代之间放置分隔符。

    案例演示:

    1).在EmployeeMapper接口中加入一个方法,如下所示:

    public List<Employee> getEmpsByConditionForeach(@Param("ids") List<Integer> ids);


    2).在MyBatis的sql映射文件中写相应的代码:

    <!-- public List<Employee> getEmpsByConditionForeach(List<Integer> ids); -->
    <select id="getEmpsByConditionForeach" resultType="com.neuedu.entity.Employee">
      select * from tbl_employee where id in
      <!-- 
        collection:指定要遍历的集合
        item:将当前遍历出的元素赋值给指定的变量
        separator:每个元素之间的分隔符
        open:遍历出所有记过拼接一个开始的字符
        close:遍历出所有结果拼接一个结束的字符
      -->
      <foreach collection="ids" open="(" close=")" separator="," item="id">
        #{id}
      </foreach>
    </select>


    3).测试类代码为:

    @Test
    public void testGetEmployee(){
      EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
      List<Integer> asList = Arrays.asList(1,2,3,4);
      List<Employee> emps = mapper.getEmpsByConditionForeach(asList);
      for (Employee employee : emps) {
        System.out.println(employee);
      }
    }
    

      

    foreach标签还可以用于批量保存数据,如下所示:

    1).在EmployeeMapper接口类中添加批量插入的方法:

    public void addEmps(@Param("emps") List<Employee> emps);
    

      

    2).在EmployeeMapper.xml的sql映射文件中添加响应的语句:

    <!-- public void addEmps(@Param("emps") List<Employee> emps); -->
    <!-- MySQL下批量保存:可以foreach遍历,mysql支持values(),(),()语法 -->
    <insert id="addEmps">
    INSERT INTO tbl_employee(user_name,gender,email,d_id) VALUES
    <foreach collection="emps" item="emp" separator=",">
    (#{emp.userName},#{emp.gender},#{emp.email},#{emp.depart.id})
    </foreach>
    </insert>
    

      

    3).测试代码:

    @Test
    public void testGetEmployee(){
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    List<Employee> emps = new ArrayList<Employee>();
    emps.add(new Employee(0, 1, "allen", "allen@163.com", new Department(1)));
    emps.add(new Employee(0, 0, "tom", "tom@163.com", new Department(2)));
    emps.add(new Employee(0, 1, "mux", "mux@163.com", new Department(1)));
    mapper.addEmps(emps);
    }
    


    二、MyBatis-缓存机制

    MyBatis 包含一个非常强大的查询缓存特性,它可以非常方便地配置和定制。缓存可以极大的提升查询效率。
    MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存。

    一级缓存(本地缓存):

    SqlSession级别的缓存,一级缓存是一致开启的,没法关闭。方法之间不共用!
    与数据库同一次会话期间查询到的数据放在本地缓存中。
    以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库;

    二级缓存(全局缓存):

    –1、默认情况下,只有一级缓存(SqlSession级别的缓存,也称为本地缓存)开启。
    –2、二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    –3、为了提高扩展性。MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存。

     

    案例:

    测试一级缓存【默认是开启的本地缓存的】:

    @Test
    public void testGetEmployee(){
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    
    Employee emp = mapper.getEmployeeById(2);
    System.out.println(emp);
    Employee emp2 = mapper.getEmployeeById(2);
    System.out.println(emp2);
    
    System.out.println(emp == emp2);
    }
    

      

    一级缓存失效的情况【4种】(没有使用到当前一级缓存的情况,效果就是,还需要再向数据库发出查询):

    1.sqlSession不同。

    @Test
    public void testGetEmployee() throws IOException{
    SqlSessionFactory sessionFactory = testBefore();
    SqlSession openSession= sessionFactory.openSession();
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Employee emp = mapper.getEmployeeById(2);
    System.out.println(emp);
    
    SqlSession openSession2= sessionFactory.openSession();
    EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
    Employee emp2 = mapper2.getEmployeeById(2);
    System.out.println(emp2);
    
    System.out.println(emp == emp2);
    
    openSession.close();
    openSession2.close();
    }


    2.SqlSession相同,但是查询条件不一样[当前缓存中还没有这个数据]

    @Test
    public void testGetEmployee() throws IOException{
    SqlSessionFactory sessionFactory = testBefore();
    SqlSession openSession= sessionFactory.openSession();
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Employee emp = mapper.getEmployeeById(2);
    System.out.println(emp);
    
    Employee emp2 = mapper.getEmployeeById(3);
    System.out.println(emp2);	
    System.out.println(emp == emp2);
    openSession.close();
    }
    

      

    3.SqlSession相同,但是两次查询之间执行了增删改操作【这次增删改可能对当前数据有影响】。

    @Test
    public void testGetEmployee() throws IOException{
    SqlSessionFactory sessionFactory = testBefore();
    SqlSession openSession= sessionFactory.openSession();
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Employee emp = mapper.getEmployeeById(2);
    System.out.println(emp);
    mapper.updateEmp(new Employee(1, 1, "张三丰","zhangsanfeng@163.com", new Department(1)));
    Employee emp2 = mapper.getEmployeeById(2);
    System.out.println(emp2);	
    System.out.println(emp == emp2);
    openSession.close();
    }
    

      

    4.SqlSession相同,手动清除了一级缓存[缓存清空]。

    @Test
    public void testGetEmployee() throws IOException{
    SqlSessionFactory sessionFactory = testBefore();
    SqlSession openSession= sessionFactory.openSession();
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    Employee emp = mapper.getEmployeeById(2);
    System.out.println(emp);
    //手动清空缓存
    openSession.clearCache();
    Employee emp2 = mapper.getEmployeeById(2);
    System.out.println(emp2);	
    System.out.println(emp == emp2);
    openSession.close();
    }


    二级缓存:

    【全局缓存】:基于namespace级别的缓存:一个namespace对应一个二级缓存。
    【一级缓存的范围还是太小了,每次SqlSession一关闭,一级缓存中的数据就消失】
    所以从这个角度讲:能跨sqlSession的缓存为二级缓存!

    工作机制:

    1、一个会话,查询一条数据,这个数据就会被放在当前会话的一级缓存中。
    2.如果会话关闭,一级缓存中的数据会被保存到二级缓存中;新的会话查询信息,就可以参照二级缓存中。
    3.SqlSession === EmployeeMapper ===> Employee
    DepartmentMapper ===> Department
    不同namespace查出的数据会放在自己对应的缓存中(map)
    效果:数据会从二级缓存中获取
    查出的数据都会被默认先放在一级缓存中。
    只有会话提交或者关闭之后,一级缓存中的数据才会转移到二级缓存中。
    需要注意的是:在哪个Mapper.xml文件中开启了<cache>缓存标签,哪个Mapper中就开启了二级缓存。

    使用:

    1).在MyBatis全局配置文件中开启全局二级缓存配置:

    <setting name="cacheEnabled" value="true"/>
    

    2).去mapper.xml中配置使用二级缓存:

    <cache eviction="FIFO" flushInterval="60000" readOnly="false" size="1024" type=""></cache>
    <!-- 
    eviction=“FIFO”:缓存回收策略:
    LRU –最近最少使用的:移除最长时间不被使用的对象。
    FIFO –先进先出:按对象进入缓存的顺序来移除它们。
    SOFT –软引用:移除基于垃圾回收器状态和软引用规则的对象。
    WEAK –弱引用:更积极地移除基于垃圾收集器状态和弱引用规则的对象。
    默认的是LRU。
    flushInterval:缓存刷新间隔
    缓存多长时间清空一次,默认不清空,设置一个毫秒值。
    size:引用数目,正整数
    代表缓存最多可以存储多少个对象,太大容易导致内存溢出
    readOnly:是否只读,true/false	
    true:只读缓存;mybatis认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。
    mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户。不安全,速度快。
    false:非只读:mybatis觉得获取的数据可能会被修改。
    mybatis会利用序列化&反序列化的技术克隆一份。安全,速度慢。
    type:指定自定义缓存的全类名
    实现cache接口即可!
    -->
    

    3).我们的POJO需要实现序列化接口[implements Serializable]

    测试二级缓存【测试代码】:

    @Test
    public void testGetEmployee() throws IOException{
    SqlSessionFactory sessionFactory = testBefore();
    //开启两个会话
    SqlSession openSession= sessionFactory.openSession();
    SqlSession openSession2 = sessionFactory.openSession();
    //利用两个openSession对象获取两个mapper对象
    EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
    EmployeeMapper mapper2 = openSession2.getMapper(EmployeeMapper.class);
    //用第一个openSession获取的mapper对象查询2号员工信息
    Employee emp = mapper.getEmployeeById(2);
    System.out.println(emp);
    //关闭第一个openSession对象
    openSession.close();
    //用第二个openSession获取的mapper对象查询2号员工信息
    Employee emp2 = mapper2.getEmployeeById(2);
    System.out.println(emp2);	
    openSession2.close();
    
    }
    
    //可以看到只发送了一次SQL语句,第二次查询时从二级缓存中拿到的数据,并没有发送新的sql语句。
    //需要注意的是:只有一级缓存中关闭的情况下,二级缓存才会被使用。

    需要注意的是:在哪个Mapper.xml文件中开启了<cache>缓存标签,哪个Mapper中就开启了二级缓存。可用DepartmentMapper.xml验证


    和缓存有关的设置/属性:
    1)cacheEnabled="true": false:关闭缓存(二级缓存关闭)【一级缓存一直可用】
    2)每个select标签都有useCache="true";
    false:不使用缓存(一级缓存依然使用,二级缓存不使用)
    3)每个增删改标签都有一个flushCache="true":增删改执行完成后就会清楚缓存【一级二级缓存都会被清空】
    查询标签:flushCache = "false"
    如果flushCache = true;每次查询之前都会清空缓存,缓存是没有被使用!

    三、MyBatis逆向工程:

    1.是什么?

      MyBatis官方提供的逆向工程,可以将单表生成常用的Mapper、Entity等配置

    2.能干嘛?

      从数据库表反向生成mapper.java/mapper.xml/entity/辅助查询类

    3.怎么玩?

    (1)导入逆向工程jar包

    mybatis-3.2.3.jar
    mybatis-generator-core-1.3.2.jar
    mysql-connector-java-5.1.28-bin.jar

    (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>
    
    	<!-- 无Example等内容(自己选择有无Example) -->
    	<context id="Mysql" targetRuntime="MyBatis3Simple"
    		defaultModelType="flat">
    
    
    		<!-- 有Example查询条件内容 -->
    		<!-- <context id="testTables" targetRuntime="MyBatis3"> -->
    		<commentGenerator>
    			<!-- 是否去除自动生成的注释 true:是 : false:否 -->
    			<property name="suppressAllComments" value="true" />
    		</commentGenerator>
    
    		<!--数据库连接的信息:驱动类、连接地址、用户名、密码 (自己修改)-->
    		<jdbcConnection 
    			driverClass="com.mysql.jdbc.Driver"
    			connectionURL="jdbc:mysql://localhost:3306/user" 
    			userId="root"
    			password="123456">
    		</jdbcConnection>
    
    		<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL 
    			和 NUMERIC 类型解析为java.math.BigDecimal -->
    		<javaTypeResolver>
    			<property name="forceBigDecimals" value="false" />
    		</javaTypeResolver>
    
    		<!-- targetProject:生成Entity类的路径 -->
    		<javaModelGenerator targetProject=".src"
    			targetPackage="com.neuedu.mybatis.entities">
    			<!-- enableSubPackages:是否让schema作为包的后缀 -->
    			<property name="enableSubPackages" value="false" />
    			<!-- 从数据库返回的值被清理前后的空格 -->
    			<property name="trimStrings" value="true" />
    		</javaModelGenerator>
    
    		<!-- targetProject:XXXMapper.xml映射文件生成的路径 -->
    		<sqlMapGenerator targetProject=".src"
    			targetPackage="com.neuedu.mybatis.mapper">
    			<!-- enableSubPackages:是否让schema作为包的后缀 -->
    			<property name="enableSubPackages" value="false" />
    		</sqlMapGenerator>
    
    		<!-- targetPackage:Mapper接口生成的位置 -->
    		<javaClientGenerator type="XMLMAPPER"
    			targetProject=".src" targetPackage="com.neuedu.mybatis.mapper">
    			<!-- enableSubPackages:是否让schema作为包的后缀 -->
    			<property name="enableSubPackages" value="false" />
    		</javaClientGenerator>
    
    		<!-- 数据库表名字和我们的entity类对应的映射指定(需自己修改) -->
    		<table tableName="person" domainObjectName="Person" />
    		<table tableName="stu" domainObjectName="Stu" />
    
    		<!-- 有些表的字段需要指定java类型 <table schema="" tableName=""> <columnOverride column="" 
    			javaType="" /> </table> -->
    	</context>
    </generatorConfiguration>

    (3)导入Mbg_GeneratorUtil.java类(不需要修改):

    package com.neuedu.mybatis.mbg;
    
    import java.io.File;
    import java.util.ArrayList;
    import java.util.List;
    
    import org.mybatis.generator.api.MyBatisGenerator;
    import org.mybatis.generator.config.Configuration;
    import org.mybatis.generator.config.xml.ConfigurationParser;
    import org.mybatis.generator.internal.DefaultShellCallback;
    
    public class Mbg_GeneratorUtil
    {
    	public void generator() throws Exception
    	{
    		List<String> warnings = new ArrayList<String>();
    		boolean overwrite = true;
    		//指定 逆向工程配置文件
    		File configFile = new File("generatorConfig.xml"); 
    		ConfigurationParser cp = new ConfigurationParser(warnings);
    		Configuration config = cp.parseConfiguration(configFile);
    		DefaultShellCallback callback = new DefaultShellCallback(overwrite);
    		MyBatisGenerator myBatisGenerator = new MyBatisGenerator(config,callback, warnings);
    		myBatisGenerator.generate(null);
    
    	} 
    	
    	public static void main(String[] args) throws Exception 
    	{
    		try 
    		{
    			new Mbg_GeneratorUtil().generator();
    		} catch (Exception e) {
    			e.printStackTrace();
    		}
    	}
    } 

    (4)直接运行程序,然后在项目名点击右键——>刷新,就会出现生成的包、类和mapper配置文件

  • 相关阅读:
    [程序员代码面试指南]栈和队列-单调栈结构(单调栈)
    快学Scala第一部分
    Add Digits
    Nim Game
    将分布式中多台节点的日志信息集中到一个节点上
    Eclipse调试的一些小技巧
    Maven的常用命令
    Eclipse插件本地扩展安装
    Spark应用程序的运行框架
    Spark运行各个时间段的解释
  • 原文地址:https://www.cnblogs.com/Mr-zhaoz/p/7489465.html
Copyright © 2020-2023  润新知