Mybatis----Mybatis使用

对映射文件(mapper.xml说明)

 //namespace:命名空间
    

 

MyBatis 使用

  增删改查

    增


    INSERT INTO tb_user (
      id,
      username,
      password,
      phone,
      email,
      created,
      updated
    )
    VALUES
      (
        #{id},
        #{username},
        #{password},
        #{phone},
        #{email},
        #{created},
        #{update}
      )

    mysql自增主键:返回主键(主键被设置到了传入参数的user上)

    
          //order:表示这个方法在插入之后执行
            select LAST_INSERT_ID();
        
        INSERT INTO t_user (username, PASSWORD)
        VALUES
        (#{username}, #{password});
    

    删


    DELETE FROM tb_user WHERE id = #{id}

    改


    UPDATE
      tb_user
    SET
      username = #{username},
      password = #{password},
      phone = #{phone},
      email = #{email},
      created = #{created},
      updated = #{update}
    WHERE id = #{id}

        测试

使用
User user = tbUserDao.getUserById(1);
user.setName="dd";
tbUserDao.updata(user);

  查

  模糊查询

 

MyBatis 动态 SQL

  注意事项

  在 mapper 的动态 SQL 中若出现大于号(>)、小于号(<)、大于等于号(>=),小于等于号(<=)等符号,最好将其转换为实体符号。否则,XML 可能会出现解析出错问题。

特别是对于小于号(<),在 XML 中是绝对不能出现的。否则,一定出错。

 

 Mybatis----Mybatis使用_第1张图片

 

  if 标签

对于该标签的执行,当 test 的值为 true 时,会将其包含的 SQL 片断拼接到其所在的 SQL 语句中。

本例实现的功能是:查询出满足用户提交查询条件的所有学生。用户提交的查询条件可以包含一个姓名的模糊查询,同时还可以包含一个年龄的下限。当然,用户在提交表单时可能两个条件均做出了设定,也可能两个条件均不做设定,也可以只做其中一项设定。

这引发的问题是,查询条件不确定,查询条件依赖于用户提交的内容。此时,就可使用动态 SQL 语句,根据用户提交内容对将要执行的 SQL 进行拼接。

  映射文件

  为了解决两个条件均未做设定的情况,在 where 后添加了一个“1=1”的条件。这样就不至于两个条件均未设定而出现只剩下一个 where,而没有任何可拼接的条件的不完整 SQL 语句。




    
    

  where 标签

   标签的中存在一个比较麻烦的地方:需要在 where 后手工添加 1=1 的子句。因为,若 where后的所有  条件均为 false,而 where 后若又没有 1=1 子句,则 SQL 中就会只剩下一个空的 where,SQL 出错。所以,在 where 后,需要

添加永为真子句 1=1,以防止这种情况的发生。但当数据量很大时,会严重影响查询效率。

  映射文件



  示例:模糊查询(组合搜索引擎)

    

   示例:模糊查询(单个搜索引擎)

     

  choose 标签

  该标签中只可以包含  ,可以包含多个  与一个 。它们联合使用,完成 Java 中的开关语句 switch..case 功能。

  本例要完成的需求是,若姓名不空,则按照姓名查询;若姓名为空,则按照年龄查询;若没有查询条件,则没有查询结果。

  映射文件


  foreach 标签(遍历数组)

   标签用于实现对于数组与集合的遍历。对其使用,需要注意:

  • collection 表示要遍历的集合类型(或者对象的属性名),这里是数组,即 array。

  • opencloseseparator 为对遍历内容的 SQL 拼接(注意拼接的含义)

  映射文件

    动态 SQL 的判断中使用的都是 OGNL 表达式。OGNL 表达式中的数组使用 array 表示,数组长度使用 array.length 表示。

    注意:如果传入的直接是一个数组或者集合,下面代码中的名字不能乱写,如果传入一个pojo,pojo对象属性是集合或者数组,那么下面的名字就是属性名;

    1、sql:SELECT * FROM t_user WHERE id IN (3,4); open:"(",close:")",separator(分割这些数据)是","

Mybatis----Mybatis使用_第2张图片


  如果sql:SELECT * FROM t_user WHERE (id=3 OR id =4);  open:"(",close:")",separator(分割这些数据)是"OR"

  foreach 标签(遍历集合)

  遍历集合的方式与遍历数组的方式相同,只不过是将 array 替换成了 list

  sql 标签

   标签用于定义 SQL 片断,以便其它 SQL 标签复用。而其它标签使用该 SQL 片断, 需要使用  子标签。该  标签可以定义 SQL 语句中的任何部分,所以  子标签可以放在动态 SQL 的任何位置。 

  映射文件


    SELECT
        id,
        name,
        age,
        score   //结尾不能有逗号,注意
    FROM
      student

  使用



Mybatis----Mybatis使用_第3张图片

resultMap

  使用场景1;

  当我们查询的结果中的字段和pojo中的属性不一致,这就导致返回映射不成功;

  我们假设User中的id字段是id_和username_,而数据库中的返回的字段是id和username;

  方式1:返回值还是resultType,通过别名,修改查询到的数据字段;

    

  方式2:使用resultMap 

    
    
        
         //pojo属性是id_
        
        
    
    

  使用场景2

  如果一个pojo对象中有属性是一个对象,我们可以利用resultMap,将值传入对象中

  方式1:不使用association(这个需要sql语句进行连表操作,一次性将多个表的数据都查出来)

    
        
        
        
        
        

        
        
        
    

  方式2:使用association (这个需要sql语句进行连表操作,一次性将多个表的数据都查出来)

    
        
        
        
        
        

        
          //property 是pojo中定义的字段
              //相当于order.id
             //相当于order.orderName
        
      

  使用场景3

  一对多查询

  比如我们需求,查询user对象中所有的user_detail数据;

SELECT 
t_user.*,
user_detail.`id` AS user_detail_id, //注意:这需要些别名,防止和id冲突
user_detail.`desc`,
user_detail.`user_id`
FROM
  t_user,
  user_detail 
WHERE t_user.id=user_detail.`user_id`

Mybatis----Mybatis使用_第4张图片

 

   使用ResultMap中的属性,来将数据装载到集合中;

    
        
        
        
        
        

        
        
              //需要有唯一标识
            
        
    
    

  多对多查询【多个一对多】(就是简单的在集合中套一个集合):注意理解集合中的每一个元素都是一个pojo,相当于给每一个pojo设值


            

  补充理解:多对多,本质来说就是一对多,因为分析数据的时候,是按照某一张表(可以看做POJO)分析的,比如说A表和B表,知道一个A对应多个B,ok,我们就够了,如果我们需求从A中获取对应的所有的B,我们就不需要知道B对A是一对一还是一对多,没有意义,如果需求,我们需要从B中获取所有的A,那么我们就可以直接分析B对A的关系了,更不就不需要分析A对B的关系。

我们所谓的多对多是两张表之间的关系,但是真正在进行写sql语句的时候,我们只需要关注一条线,起点A-->终点B。或者A--->B----->C(B的数据放在A存,C的数据放在B存,注意上面的A,B,C,都是POJO)

  使用场景4

  resultMap的继承,rusultMap可以继承另一个resultMap,然后就不需要写在resultMap中写继承的代码了(注意继承,是全部继承的,如果继承中的某些东西不需要,那么就不要继承了)

 

延迟加载

  延迟加载:先从单表查询,需要是在从关联表去关联查询,大大提高数据库性能,如果我们不使用下面案例这个框架,用最简单的方法实现延迟加载本质,就是假如我们只需要用户表中的数据,不需要订单表中的数据,我们就写一个sql查询用户表数据即可,如果突然又需要user表中关联的order信息,我们就在用之前user查询的结果在去查询order信息,这就是延迟加载的本质。

resultMap中的association,collection 可以实现延迟加载

  环境配置

  mybatis的配置文件


  Mapper.xml

    
    

    
    
        
        
        

        
        
        
    

    
    

  测试

  dao

public interface UserDao {
    public User selectUserById(int id);
}

  测试

    @Test
    public void userTest(){
        //注意如果是Debug模式测试,延迟加载就会失效(Debug会直接将order数据查询出来赋值给user)
        User user = userDao.selectUserById(3);
        System.out.println(user.getId());
        System.out.println("------------------------");
        /*当执行下面的语句的时候,控制台才出现查询订单的sql,所有如果不是必要,不要打印user,一旦打印了,延迟加载的sql也会被执行(原因需要获得user所有的信息)*/
        System.out.println(user.getOrder());
    }

 

查询缓存

  Mybatis提供了查询缓存,用于减轻数据库压力,提高数据库性能

  Mybatis提供了一级缓存和二级缓存

Mybatis----Mybatis使用_第5张图片

 

  为什么使用缓存?

  如果sql对应的数据在缓存中存在,那么我们就不需要在查询数据库了,直接将缓存中的数据返回给用户。提高系统的性能

  4.1 一级缓存(默认开启)

一级缓存:是Sqlsession级别的缓存。在操作数据库时需要构造soglsession对象,在对象中有一个localCache,他是一个(HashMap)用于存储缓存数据。HaspMap的key就是封装了namespace和statement id 以及拼接好了的sql,value是缓存的数据;

不同的sglsession之间的缓存数据区域(HashMap)是互相不影响的。

Mybatis----Mybatis使用_第6张图片

  原理:第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。如果sqlsession去执行commit 操作(执行插入、更新、删除),清空Sqlsession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。第二次发起查询用户id为1的用户信息,先去找缓存中是否有d为1的用户信息,缓存中有,直接从缓存中获取用户信息。没有就去读取数据库,然后将数据写入一级缓存。

  测试一级缓存

我使用的是sping整合mybatis,这样sqlsession自动生成的,我测试的时候,每次执行完毕sql后,通过打印日志看到sqlsession就会自动关闭,下一个请求后,就又自动生成一个sqlsession,所以每次的sqlsession都不一样,这样的话,就利用不了一级缓存了,因为一级缓存是在一个sqlsession里面。但是我们开启事务后,就可以利用一级缓存了。原因是开启事务后,事务控制在service(mvc三层结构),当调用service方法后,就开启了事务,会创建一个sqlsession,并且整个service方法这个过程中都是使用的这一个sqlsession。但是一旦service方法执行完毕,sqlsession就关闭了。所以一级缓存时间很短的,service方法调用完毕后,就清空了。如果我们就需要使用二级缓存长时间缓存数据。

参考spring一级缓存的详细说明:https://blog.csdn.net/ctwy291314/article/details/81938882

 

  4.2 二级缓存(默认不开启)

  二级缓存:是mapper级别的缓存,多个Sqlsession.去操作同一个Mapper的sql语句,多个Sqlsession.可以共用一个二级缓存,二级缓存是跨Sqlsession的

  二级缓存应用场景:1、对访问的请求次数多,但是查询的结果的实时性不高的数据。,2、像一些特别耗时的统计分析sql(查询一条sql可能要几十分钟),实现方法:(设置flushInterval,每隔一段时间,自动清空缓存)

  二级缓存局限性?二级缓存对细粒度的数据级别的缓存实现不好,比如,一大批用户查询了1万条的商品信息,放在缓存中,但是某一个商品信息被修改了,就会造成缓存数据被清空,1万条数据清空。

所以我们需要是,改了哪一个商品,就把那一个商品的缓存数据改变,Mybatis实现不了这种需求,需要我们手动实现业务层缓存,就是所谓的三级缓存(概念)。

 

Mybatis----Mybatis使用_第7张图片

  二级缓存区的划分?

  是按照namespace划分的(也就是按照dao接口,namespace就是dao接口的全限定路径):每一个namespace有自己的二级缓存空间。不同的sqlsession访问同一个namespace,才数据共享。

比如下面两个dao,sqlsession之后访问同一个dao中相同或者不同的方法,才会共享二级缓存(依旧是HashMap存储数据)。如果mapper文件的namespace(命名空间)一样,那样,他们就可以共享一个mapper缓存。

为什么要划分呢?我自己也不太理解,把所有的数据都放到一个缓存空间不一样吗?等到后续的查阅资料。

  UserDao

@Repository
public interface UserDao {
    public User selectUserById(int id);
    public List selectAllUser(int id);
}

  OrderDao

@Repository
public interface OrderDao {
    public Order selectOrderById(int id);
}

 

  使用二级缓存

  开启二级缓存

  1、mybtis-config.xml

  //设置为true

  2、mapper.xml


      //开启缓存(UserDao调用的所有的方法,公用这一个二级缓存)

  3、将查询的pojo实现序列化接口

 

测试

  模拟多个sqlsession;缓存是长时间存在内存中的。

    public void sqlsession(UserDao userDao){
        User user = userDao.selectUserById(3);
        System.out.println(user.getId());
    }
    public void sqlsession2(UserDao userDao){
        User user = userDao.selectUserById(3);  //只有调用同一个方法,传入同一个参数,用同一个namespace,sql才会一样,此时就不会再去查询数据库了(如果没有执行增删改操作)
        System.out.println(user.getId());
    }
    //注意测试的时候不用开启事务,否则就使用了一级缓存了(同一个sqlsession),测试不出来结果了
    //@Transactional
    @Test
    public void userTest(){
        new DemoTest().sqlsession(userDao);  //当调用这个方法结束后,sqlsession就自动关闭了(注意:一定需要关闭,否则查询的数据不会写入到二级缓存中)
        new DemoTest().sqlsession2(userDao);
    }

  

补充