Mybatis

Mybatis

一、引言

web应用架构: 单体架构, 垂直架构, 分布式架构

垂直架构分层:
  数据层: 负责处理数据库操作.
    包名: dao
    底层技术: jdbc
    代表框架: mybatis, hibernate
  业务逻辑层: 负责业务流程处理和代码增强
    包名: service
    底层技术: 无
    代表框架: spring
  展现层: 负责业务控制和视图
    包名: action, controller
    底层技术: servlet
    代表框架: springmvc, struts2

二、初始Mybatis

概念:

MyBatis 是一款优秀的持久层框架

MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集的过程

MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类 【Plain Old Java Objects,普通的 Java对象】映射成数据库中的记录。

MyBatis 本是apache的一个开源项目ibatis, 2010年这个项目由apache 迁移到了google code,并且改名为MyBatis 。

2013年11月迁移到Github .

Mybatis官方文档 : http://www.mybatis.org/mybatis-3/zh/index.html

GitHub : https://github.com/mybatis/mybatis-3

作用:

· Mybatis就是帮助程序猿将数据存入数据库中 , 和从数据库中取数据 .

· 传统的jdbc操作 , 有很多重复代码块 .比如 : 数据取出时的封装 , 数据库的建立连接等等, 通过框架可以减少重复代码,提高开发效率 .

· MyBatis 是一个半自动化的ORM框架 (Object Relationship Mapping) -->对象关系映射

· 所有的事情,不用Mybatis依旧可以做到,只是用了它,所有实现会更加简单!技术没有高低之分,只有使用这个技术的人有高低之别

优点:

·简单易学:本身就很小且简单。没有任何第三方依赖,最简单安装只要两个jar文件+配置几个sql映射文件就可以了,易于学习,易于使用,通过文档和源代码,可以比较完全的掌握它的设计思路和实现。

·灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。

· 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离,使系统的设计更清晰,更易维护,更易单元测试。sql和代码的分离,提高了可维护性。

· 提供xml标签,支持编写动态sql。

· 最重要的一点,使用的人多!公司需要!

三、使用

0. 环境准备:

        1) sql

        2) jar包

        3) mybatis配置文件(3个主要内容)

    1. 编写与数据表对应的实体类.

    2. 编写数据接口.

        包名: dao

        接口命名: 实体类名+Mapper, 实体类名+Dao. 例: UserMapper

    3. 编写数据接口映射文件. (重要)

        文件位置: resources下的dao文件夹中. 该文件路径要求与数据接口所在包一致

        命名: 数据接口名.xml

        引用: 每有一个数据接口映射文件, 都需要在mybatis配置文件中进行引用

        数据接口映射文件的作用: 在mybatis代码运行时, 会将数据接口映射文件解析成对应数据接口的实现类.

        接口映射文件的内容:4大sql标签, 结果映射标签

四、动态SQL


动态SQL: 通过mapper映射文件提供的标签, 达到根据参数具体情况决定最终执行的sql语句的目的

常用的标签:
1. <if test="">判断结果为true时, 向整个sql中添加的内容</if>
根据test属性中的判断结果, 决定是否添加指定的sql内容.
2. <set></set>
用于取代set关键字. 自动去除赋值语句中多余的","
如果最终set标签中, 没有任何一条赋值语句, 则set标签不会被翻译成set关键字
3. <where></where>
用于取代where关键字.
自动去除条件语句中多余的"and"或"or"
如果, 最终where标签中, 没有任何一条条件语句, 则where抱歉不会被翻译成where关键字
4. <bind name="参数名" value="新的值">
5. <choose>
<when test=""></when>
<when test=""></when>
....
<otherwise></otherwise>
</choose>
6. <foreach collection="需要遍历的集合或数组" item="临时变量" separator="循环内容之间的分隔符"
open="循环开始前的内容" close="循环结束后的内容"></foreach>

五、关联查询

关联查询: 多对多

    主表1: 一

    中间表: 多

    主表2: 一

 

    多对多的使用步骤:

        1. 在实体类

            i.  在主表对应实体类中添加成员变量.

                    成员变量的数据类型: List

                    泛型类型: 另一个主表对应的实体类型

            ii. get/set方法

 

        2. 接口映射文件

            i.  在结果集映射中, 添加collection标签

            ii. 设置collection标签的property属性. 属性值是实体类中的代表另一个主表的成员变量的变量名

            iii.设置collection标签的column属性. 属性值是主键字段名. 表示执行关联查询时, 携带到查询语句中的参数

            iv. 设置collection标签的select属性. 属性值是用于查询另一个主表数据的查询语句的id

            v.  编写用于查询另一个主表对象集合的查询语句, id与collection标签的select属性值一致.

                resultMap属性引用的结果集映射id需要额外声明

            vi. 添加新的结果集映射, id需要被 v. 的查询语句引用

 

 

 

关联查询 多对一:

    前提: 两张数据表之间具有事实上的引用关系

    使用的标签: collection, association

    标签使用的位置: 在结果集映射的末尾声明

 

    多: 引用的一方, 外键所在的表, 子表

    一: 被引用的一方, 主表

 

    使用步骤:

        1. association的使用步骤: (多对一)

            1) 在实体类中:

                i.  添加属性

                    数据类型: 一方的实体类型

                ii. 添加get/set方法

            2) 在数据接口映射文件中:

                i.  在结果集映射中, 添加association标签

                ii. 设置association标签的property属性. 属性值是实体类中的代表一方的成员变量的变量名

                iii.设置association标签的column属性. 属性值是外键字段名. 表示执行关联查询时, 携带到查询语句中的参数

                iv. 设置association标签的select属性. 属性值是用于查询一方数据的查询语句的id

                v.  编写用于查询一方对象的查询语句, id与association标签的select属性值一致.

                    resultMap属性引用的结果集映射id需要额外声明

                vi. 添加新的结果集映射, id需要被 v. 的查询语句引用

        2. collection的使用步骤: (一对多)

            1) 在实体类中:

                i.  添加属性

                    数据类型: List

                    泛型: 多方的实体类型

                ii. 添加get/set方法

            2) 在数据接口映射文件中:

                i.  在结果集映射中, 添加collection标签

                ii. 设置collection标签的property属性. 属性值是实体类中的代表多方的成员变量的变量名

                iii.设置collection标签的column属性. 属性值是主键字段名. 表示执行关联查询时, 携带到查询语句中的参数

                iv. 设置collection标签的select属性. 属性值是用于查询多方数据的查询语句的id

                v.  编写用于查询多方对象集合的查询语句, id与collection标签的select属性值一致.

                    resultMap属性引用的结果集映射id需要额外声明

                vi. 添加新的结果集映射, id需要被 v. 的查询语句引用

六、缓存

6.1 简介

1、什么是缓存 [ Cache ]?

· 存在内存中的临时数据。

· 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

2、为什么使用缓存?

· 减少和数据库的交互次数,减少系统开销,提高系统效率。

3、什么样的数据能使用缓存?

· 经常查询并且不经常改变的数据。

 

6.2 Mybatis缓存

· MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

· MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

§ 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

§ 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

§ 为了提高扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

 

6.3一级缓存

一级缓存也叫本地缓存:

· 与数据库同一次会话期间查询到的数据会放在本地缓存中。

· 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

 

测试

1、在mybatis中加入日志,方便测试结果

2、编写接口方法

//根据id查询用户

User queryUserById(@Param("id") int id);

 

3、接口对应的Mapper文件

<select id="queryUserById" resultType="user">
select * from user where id = #{id}
</select>

 

4、测试

@Test
public void testQueryUserById(){
  SqlSession session = MybatisUtils.getSession();
  UserMapper mapper = session.getMapper(UserMapper.class);

  User user = mapper.queryUserById(1);
  System.out.println(user);
  User user2 = mapper.queryUserById(1);
  System.out.println(user2);
  System.out.println(user==user2);

  session.close();
}

 

5、结果分析

一级缓存失效的四种情况

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

1、sqlSession不同

@Test
public void testQueryUserById(){
  SqlSession session = MybatisUtils.getSession();
  SqlSession session2 = MybatisUtils.getSession();
  UserMapper mapper = session.getMapper(UserMapper.class);
  UserMapper mapper2 = session2.getMapper(UserMapper.class);

  User user = mapper.queryUserById(1);
  System.out.println(user);
  User user2 = mapper2.queryUserById(1);
  System.out.println(user2);
  System.out.println(user==user2);

  session.close();
  session2.close();
}

 

观察结果:发现发送了两条SQL语句!

结论:每个**sqlSession中的缓存相互独立**

2、sqlSession相同,查询条件不同

@Test
public void testQueryUserById(){
  SqlSession session = MybatisUtils.getSession();
  UserMapper mapper = session.getMapper(UserMapper.class);
  UserMapper mapper2 = session.getMapper(UserMapper.class);

  User user = mapper.queryUserById(1);
  System.out.println(user);
  User user2 = mapper2.queryUserById(2);
  System.out.println(user2);
  System.out.println(user==user2);

  session.close();
}

 

观察结果:发现发送了两条SQL语句!很正常的理解

结论:当前缓存中,不存在这个数据

3、sqlSession相同,两次查询之间执行了增删改操作!

增加方法

//修改用户 int updateUser(Map map);

编写SQL

<update id="updateUser" parameterType="map">
update user set name = #{name} where id = #{id}
</update>

 

测试

@Test
public void testQueryUserById(){
  SqlSession session = MybatisUtils.getSession();
  UserMapper mapper = session.getMapper(UserMapper.class);

  User user = mapper.queryUserById(1);
  System.out.println(user);

  HashMap map = new HashMap();
  map.put("name","kuangshen");
  map.put("id",4);
  mapper.updateUser(map);

  User user2 = mapper.queryUserById(1);
  System.out.println(user2);

  System.out.println(user==user2);

  session.close();
}

 

观察结果:查询在中间执行了增删改操作后,重新执行了

结论:因为增删改操作可能会对当前数据产生影响

4、sqlSession相同,手动清除一级缓存

@Test
public void testQueryUserById(){
  SqlSession session = MybatisUtils.getSession();
  UserMapper mapper = session.getMapper(UserMapper.class);

  User user = mapper.queryUserById(1);
  System.out.println(user);

  session.clearCache();//手动清除缓存

  User user2 = mapper.queryUserById(1);
  System.out.println(user2);

  System.out.println(user==user2);

  session.close();
}

 

一级缓存就是一个map

 

10.4 二级缓存

· 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存

· 基于namespace级别的缓存,一个名称空间,对应一个二级缓存;

· 工作机制

§ 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;

§ 如果当前会话关闭了,这个会话对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;

§ 新的会话查询信息,就可以从二级缓存中获取内容;

§ 不同的mapper查出的数据会放在自己对应的缓存(map)中;

 

使用步骤

1、开启全局缓存 【mybatis-config.xml】

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

2、去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】

<cache/>

官方示例=>查看官方文档 <cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/> 这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

3、代码测试

· 所有的实体类先实现序列化接口

· 测试代码

@Test
public void testQueryUserById(){
  SqlSession session = MybatisUtils.getSession();
  SqlSession session2 = MybatisUtils.getSession();

  UserMapper mapper = session.getMapper(UserMapper.class);
  UserMapper mapper2 = session2.getMapper(UserMapper.class);

  User user = mapper.queryUserById(1);
  System.out.println(user);
  session.close();

  User user2 = mapper2.queryUserById(1);
  System.out.println(user2);
  System.out.println(user==user2);

  session2.close();
}

 

结论

· 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据

· 查出的数据都会被默认先放在一级缓存中

· 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

 

缓存原理图

 

 

 

 

posted @ 2021-01-15 14:35  派大靖  阅读(134)  评论(0)    收藏  举报