MyBatis

1简介

1.1 什么是MyBatis

  • MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。2013年11月迁移到Github。
  • MyBatis 是一款优秀的持久层框架
  • 它支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 避免了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

持久化
数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(jdbc),io文件持久化

持久层

  • 完成持久化的代码块
    -层界限十分明显

1.2如何获得MyBatis

  • maven仓库
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>3.5.6</version>
</dependency>

1.3为什么需要MyBatis

-帮助程序员将数据写入数据库
-方便
-简化代码
-优点:

  1. 简单易学,没有任何第三方依赖。
  2. 灵活, sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  3. sql和代码的分离,提高了可维护性。
  4. -提供映射标签,支持对象与数据库的orm字段关系映射
  5. -提供对象关系映射标签,支持对象关系组建维护
  6. -提供xml标签,支持编写动态sql

2 第一个MyBatis项目

思路:环境搭建——>导入MyBatis——>编写代码——>测试

编写Mybatis的核心配置文件mybatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration:核心配置文件-->
<configuration>
    <!--引入外部配置文件-->
    <properties resource="db.properties" />
    <!--可以给实体类起别名-->
    <typeAliases>
        <package name="top.yqqk.pojo"/>
    </typeAliases>
    <!--environments:环境-->
    <environments default="development"> <!--默认开发环境-->
        <environment id="development">
            <transactionManager type="JDBC"/><!--事务管理-->
            <dataSource type="POOLED">
                <property name="driver" value="${driver}"/><!--驱动-->
                <!--数据库连接地址-->
                <property name="url" value="${url}"/>
                <property name="username" value="${username}"/>
                <property name="password" value="${password}"/>
            </dataSource>
        </environment>
    </environments>
    <!--每一个Mapper.xml都需要在mybatis核心配置文件中注册-->
    <mappers>
        <mapper class="top.yqqk.mapper.UserMapper"/>
    </mappers>
</configuration>

1.创建一个普通的maven项目
2.导入maven依赖

<!--连接数据库 数据库驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.23</version>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <!--junit  单元测试工具-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>

3.编写mybatis工具类

package top.yqqk.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

//SqlSessionFactory -->SqlSession
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory =null;
    static {
        try {
            //使用Mybatis第一步创建:sqlSessionFactory对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
    // SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

4.编写代码

  • 实体类
package top.yqqk.pojo;

//实体类
public class User {
    private int id;
    private String username;
    private String password;

    public User() {
    }

    public User(int id, String username, String password) {
        this.id = id;
        this.username = username;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

5.编写测试类

package top.yqqk.dao;

import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import top.yqqk.pojo.User;
import top.yqqk.utils.MybatisUtils;

import java.util.List;

public class UserDaoTest {
    @Test
    public void test(){
        //第一步 获得sqlSession对象
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        //方式一:gerMapper
        UserDao userDao = sqlSession.getMapper(UserDao.class);
        List<User> userList = userDao.getUserList();
        for (User user:userList) {
            System.out.println(user);
        }
        //关闭sqlSession
        sqlSession.close();
    }

}

6.可能遇到的问题
maven资源导出失败;
配置文件没有注册;
绑定接口错误;
方法名不对;
返回类型不对;

3 CRUD(增查改删)

1.namespace

namespace的包名要dao/mapper接口中的包名一致

2.select

  • id:就是对应namespace中的方法名
  • resultType:sql语句的返回值
  • parameterType:参数类型
 <!--select 查询语句:查询所有的用户-->
    <!--id :绑定对应方法的名字-->
    <!--resultType:结果集-->
    <select id="getUserList" resultType="top.yqqk.pojo.User" >
        select * from user
    </select>

3.insert

<!--添加用户-->
    <insert id="addUser" parameterType="top.yqqk.pojo.User" >
        insert into user (id,username,password)values (#{id},#{username},#{password})
    </insert>

4.update

<!--修改用户-->
    <update id="updateUser" parameterType="top.yqqk.pojo.User">
        update user set username= #{username},password=#{password} where id=#{id}
    </update>

5.delete

<!--删除用户-->
    <delete id="deleteUser" parameterType="int">
        delete from user where id = #{id}
    </delete>

6.万能的Map

  • map传递参数,直接在sql取出key即可【 parameterType="map"】;
  • 对象传递参数,直接在sql取出对象的属性即可【 parameterType="Object"】;
  • 只有一个基本类型参数的情况下,可以直接在sql中取到【 parameterType可以不写】
  • 多个参数用map,或者注解

7.模糊查询

1.java代码执行的时候,传递通配符% %

 List<User> userLike = userDao.getUserLike("%哈%");

2.在sql拼接在使用通配符

<select id="getUserLike" resultType="top.yqqk.pojo.User">
        select * from user where username like "%#{values}%"
</select>

4 配置文件解析(mybatis-config.xml 官方推荐名称)

1.核心配置文件

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)

2.环境配置(environments)

  • MyBatis 可以配置成适应多种环境
    不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

MyBatis默认事务管理器就是JDBC,连接池:POOLED

###3.属性(properties)

我们可以通过properties属性来实现引用配置文件

这些属性可以在外部进行配置,并可以进行动态替换。你既可以在典型的 Java 属性文件中配置这些属性,也可以在 properties 元素的子元素中设置。

编写一个配置文件
db.properties

driver= com.mysql.cj.jdbc.Driver
url =jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8
username =root
password= a123

在核心文件引入

 <!--引入外部配置文件-->
 <properties resource="db.properties" />
  • 可以直接引入外部文件
  • 可以在其中增加一些属性配置
  • 如果两个文件有同一个字段,优先使用外部配置文件

4.类型别名(typeAliases)

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写

在核心文件中配置

<!--可以给实体类起别名-->
    <typeAliases>
        <typeAlias type="top.yqqk.pojo.User" alias="User" />
    </typeAliases>

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean

<!--可以给实体类起别名-->
<typeAliases>
  <package name="top.yqqk.pojo"/>
</typeAliases>

在实体类比较少的时候,使用第一种方式
如果实体类就多,使用第二种方式
第一种可以自定义(DIY)别名,第二种不行,如果非要改,需要在实体类添加注解

5.映射器(mappers)

注册绑定我们的Mapper文件

  • 方式一
<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
</mappers>
  • 方式二(不推荐)
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
  • 方式三 (使用class文件注册绑定)
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>

注意点:
接口和他的Mapper文件必须同名
接口和他的Mapper文件必须在同一个包中

  • 方式四(使用扫描包进行注入绑定)
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

注意点:
接口和他的Mapper文件必须同名
接口和他的Mapper文件必须在同一个包中

6作用域(Scope)和生命周期

作用域和生命周期是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder:

  • 一旦创建了SqlSessionFactoryBuilder,就不需要它了
  • 是局部变量

SqlSessionFactory :

  • 可以想象成数据库连接池
  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
  • SqlSessionFactory 的最佳作用域是应用作用域。
  • 最简单的就是使用单例模式或者静态单例模式。

SqlSession :

  • 连接到连接池的一个请求
  • qlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 用完需要关闭

5.解决属性名与字段名不一致的问题

解决方案

  • 在sql语句中使用别名 (让字段名别名成对应得属性名)
  • resultMap:结果集映射(重点)
<!--resultMap 标签中的id 要与resultMap的值一致  type是对应的实体类-->
<resultMap id="UserMap" type="User">
       <!--column 数据库中的字段,property 实体类中的属性 要一一对应-->
       <result column="" property=""></result>
</resultMap>
<select id="getUserList" resultMap="UserMap" >
        select * from user
</select>

6.分页

为什么要使用分页?

  • 减少数据的处理量

使用limit分页查询

语法:
<--startIndex:从哪里开始,pageSize:需要查询的数量-->
 select * FROM user limit startIndex,pageSize;
 select * FROM user limit 1,3;

使用mybatis进行分页查询,核心就是sql语句

  • 接口
 //分页查询用户
 List<User> getUserLimit(Map<String,Integer> map);
  • Mapper.xml
<!--分页查询-->
<select id="getUserLimit" resultType="User"  parameterType="map">
   select * FROM user LIMIT #{startIndex},#{pageSize}
</select>

7.使用注解开发

本质:反射机制实现
底层:动态代理!

1.注解在接口上实现

//查询全部用户
@Select("select * from user")
List<User> getUserList();

2.需要在核心配置文件中绑定接口

<!--绑定接口-->
<mappers>
    <mapper class="top.yqqk.dao.UserMapper"/>
</mappers>

3.测试

8.复杂的查询语句环境搭建

8.1 多对一

  • 多个学生,对应一个老师
  • 对于学生而言,关联 多个学生关联一个老师 【多对一】
  • 对于老师而言,集合 一个老师有多个学生 【一对多】

测试环境搭建
1.新建实体类
2.建立Mapper接口
3.建立Mapper.xml文件
4.在可信配置文件绑定注册我们的Mapper接口或者文件!
5.测试环境是否搭建成功

按照查询嵌套处理

    <!--查询所有学生以及对应的学生-->
    <!--思路 1.查询所有的学生信息 2.根据查询出来的学生的tid,寻找对应的老师!-->
    <select id="getStudent" resultMap="StudentTeacher">
        select * from student ;
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <!--复杂的属性,我们需要单独处理 对象:association 集合:collection-->
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher" />
    </resultMap>
    <select id="getTeacher" resultType="Teacher">
         select * from teacher where id = #{id} ;
    </select>

按照结果嵌套处理

    <!--==========按照结果嵌套处理==========-->
    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname
        from Student s,teacher t
        where  s.tid = t.id;
    </select>
    <resultMap id="StudentTeacher2" type="Student">
        <result property="id" column="sid" />
        <result property="name" column="sname" />
        <association property="teacher" javaType="Teacher">
            <result property="name" column="tname" />
        </association>
    </resultMap>

回顾Mysql 多对一的查询方式

  • 子查询
  • 联表查询

8.2 一对多

比如:一个老师拥有多个学生
对于老师来就是一对多的关系

按查询嵌套查询

    <!--按结果嵌套查询-->
    <select id="getTeacher2" resultMap="TeacherStudent">
        select s.id sid,s.name sname,t.id tid,t.name tname
        from teacher t,student s
        where s.tid = t.id and t.id = #{tid};
    </select>
    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

按结果嵌套查询

    <!--按查询嵌套查询-->
    <select id="getTeacher3" resultMap="TeacherStudent2">
        select * from teacher where id = #{tid}
    </select>
    <resultMap id="TeacherStudent2" type="Teacher">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
        <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" />
    </resultMap>
    <select id="getStudentByTeacherId" resultType="Student">
        select * from student where tid= #{tid}
    </select>

小结

  • 关联 association【多对一】
  • 集合 collection【一对多】
    -javaType & ofType
    1.javaType用来指定实体类中属性的类型
    2.ofType用来指定映射到List或者集合中pojo的类型,泛型中的约束类型!

注意点

  • 保证sql可读性,尽量保证通俗易懂
  • 注意一对多和多对一中,字段与属性名的问题
  • 如果问题不好排查错误,可以使用日志

9. 动态sql

动态语句就是根据不同条件生成不同的SQL语句
利用动态环境可以摆脱这种痛苦

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

if

    <--根据条件查询数据-->
    <select id="queryBlogIF" resultType="blog" parameterType="map">
        select * from blog where 1=1
        <if test="title !=null">
            and title = #{title}
        </if>
        <if test="author !=null">
            and author = #{author}
        </if>
    </select>

choose (when, otherwise)

trim (where, set)

  • where
    <!--根据条件查询数据-->
    <select id="queryBlogIF" resultType="blog" parameterType="map">
        select * from blog
        <where>
            <if test="title !=null">
                title = #{title}
            </if>
            <if test="author !=null">
                and author = #{author}
            </if>
        </where>
    </select>

SQL片段

有时候,我们可能会将一些功能的部分抽取出来,方便复用

1.使用SQL标签抽取公共的部分

<sql id="if-title-author">
	<if test="title !=null">
		title = #{title}
	</if>
	<if test="author !=null">
		and author = #{author}
	</if>
</sql>

2.在需要使用的地方使用include标签引用即可

<select id="queryBlogIF" resultType="blog" parameterType="map">
        select * from blog
        <where>
            <include refid="if-title-author"></include>
        </where>
</select>

注意事项:

  • 最好基于单表来电仪SQL语句!
  • 不要存在where标签

Foreach

<--select * from blog where 1 =1 and (id=1 or id=2 or id=3)-->
<--传递一个map,map可以存在一个集合-->
<select id="queryBlogForeach" resultType="blog" parameterType="map">
       select * from blog
       <where>
           <foreach collection="ids" item="id" open="and (" separator="or" close=")">
               id=#{id}
           </foreach>
       </where>
</select>

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了

10 缓存

10.1

1.什么是缓存【Cache】
- 存在内存中的临时数据
- 将用户经常查询的数据放在缓存中(内存),用户查询数据就不用从磁盘上(关系型数据库数据文件)查询,
从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题
2.为什么使用缓存
- 减少和数据库的交互次数,减少系统开销,提高系统效率
3.什么样的数据使用缓存

  • 经常查询并且经常不改变得数据

10.2 Mybatis缓存

  • MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
  • MyBatis默认定义了两级缓存:一级缓存和二级缓存
    • 默认情况下,只启用了本地的会话缓存(一级缓存)【SqlSession级别的缓存】
    • 要启用全局的二级缓存,需要手动开启和配置 【namespace级别的缓存】
    • 为了提高扩展性,Mybatis定义了缓存接口Cache。我们可以通过实现Cache接口来自定义二级缓存

10.3 一级缓存

  • 一级缓存也叫本地缓存:SqlSession
    • 数据库同一次会话期间查询到的数据会放在本地缓存中。
    • 以后如果需要获取相同的数据,直接从缓存中拿。没必要再去查询数据库

缓存失效的情况:
1.查询不同的东西
2.增删改操作,可能会改变原来的数据,所以必定会的刷新缓存
3.查询不同的Mapper.xml
4.手动清理缓存
clearCache()

10.4 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制:
    -一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果当前会话关闭了,这个会话对应的一级缓存就没有了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中;
    • 新的会话查询信息,就可以从二级缓存中获取类容;
    • 不同的mapper查出的数据会放在自己对应的缓存(map)中

使用二级缓存的步骤

1.开启全局缓存
image

<!--显示开启全局缓存-->
<setting name="cacheEnabled" value="true"/>

2.在要使用二级缓存的mapper.xml文件开启二级缓存

<!--在当前的mapper.xml中使用二级缓存-->
<cache/>

也可以自定义参数

<!--在当前的mapper.xml中使用二级缓存-->
<!--eviction:缓存策略;flushInterval:每隔一段时间刷新缓存;size:可以缓存多少条数据;readOnly:是不是只读-->
<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>

问题:我们需要将实体类序列化(Serializable),否则会报错
image

小结

  • 只要开启了二级缓存,在同一个Mapper下就有效果
  • 所有的数据都会先放在以及缓存中
  • 只有当前会话结束了,才会提交到二级缓存中
posted @ 2021-04-01 21:52  晚雾  阅读(54)  评论(0)    收藏  举报