MyBatis

79 阅读10分钟

MyBatis

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Ordinary Java Object,普通的 Java对象)映射成数据库中的记录。

数据库连接池

传统情况下,客户端每一次请求到达服务器,服务器如果需要访问数据库的话,先要跟数据库建立连接,再进行数据库访问,然后断开数据库连接。 这个过程是比较费时费力的。数据库连接池就是在一块内存中提前放好一堆的数据库连接对象。要使用的时候直接来取出一个使用,用完之后还回连接池中,不必销毁。 这样做酒省去了建立连接和断开连接的过程,大大的提升了访问速度。

在 Maven 中开发 Mybatis

  1. 新建 Maven 项目,新建一个模块
  2. 修改目录结构,将缺失的目录补齐
  3. 在 pom.xml 文件中添加 MyBatis 依赖和 mysql 依赖
<dependencys>
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.32</version>
    </dependency>
</dependencys>
  1. 在 pom.xml 文件中添加资源文件配置
<build>
    <resources>
    
      <resource>
        <directory>src/java/main</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
        </includes>
      </resource>
    
      <resource>
        <directory>src/java/resources</directory>
        <includes>
          <include>**/*.xml</include>
          <include>**/*.properties</include>
        </includes>
      </resource>
    
    </resources>
</build>
  1. 在 IDEA 中添加数据库可视化
  • 在 IDEA 右侧边栏中选择 Database 添加一个 MySQL 服务
  • 填写数据库名称,用户名,密码建立连接
  1. 在资源目录中添加数据库配置文件 jdbc.properties,配置内容包括(驱动,数据库链接,用户名,密码)
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/todo?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=123456
  1. 在资源目录中添加 SqlMapConfig.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>

    <!-- 读取属性文件 jdbc.properties -->
    <!--
      resource:从 resources 目录下查找指定的配置文件
      url:以绝对路劲的形式查找指定的属性文件(D:/dir/.../jdbc.properties)

      两个属性都是用于指定配置文件的路径,使用其一即可
    -->
    <properties resource="jdbc.properties" />

    <settings>
        <!-- 设置输出日志 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />
    </settings>

    <!-- 别名设置 -->
    <typeAliases>
        <!--
          设置 User 类别名为 user,在 mapper 中就可以不用写完整的 User 类名,可以使用 user 代替
          单个注册
          type:类名,包括包名,要写全
          alias:别名
        -->
        <!--<typeAlias type="com.lmb.entity.User" alias="user" />-->

        <!--
          将怎整个包中的类都注册别名,默认的别名为小驼峰形式
          例:com.lmb.entity.User -> user
             com.lmb.entity.StudentInformation -> studentInformation
        -->
        <package name="com.lmb.entity"/>
    </typeAliases>

    <!-- 配置数据库的环境变量 -->
    <!--
      default:使用哪一套配置,方便在多中环境中切换,只需要修改 default 的值即可。
    -->
    <environments default="dev">

        <!-- dev 数据库配置 -->
        <environment id="dev">
            <!--
              配属事务管理器
              type:指定事务管理的方式
                  - JDBC:事务的管理交给程序员
                  - MANAGED:事务的管理由容器(Spring)来管理
            -->
            <transactionManager type="JDBC" />
            <!--
              配置数据源
              type:指定不同的配置方式
                  - JNDI:java 命名目录接口,在服务器端进行数据库连接池的管理
                  - POOLED:使用数据库连接池
                  - UNPOOLED:不使用数据库连接池
            -->
            <dataSource type="POOLED">
                <!-- 指定数据库配置的属性(驱动,url,username,password) -->
                <property name="driver" value="${jdbc.driverClassName}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>

        <!-- pro 数据库配置 -->
        <!--<environment id="pro">
            <transactionManager type=""></transactionManager>
            <dataSource type=""></dataSource>
        </environment>-->

    </environments>

    <!-- 注册 mapper.xml 文件 -->
    <!--
      resource 和 url 属性参考上面
      class:动态代理方式下的注册
    -->
    <mappers>
        <mapper resource="UserMapper.xml" />
    </mappers>

</configuration>
  1. 添加业务功能的 xml 文件,
  2. 创建实体类,用封装装数据
  • 实体类的字段名和数据类型要跟数据库表字段完全一致
  1. 创建测试类,进行功能测试

MyBatis 编程步骤

class Test {
    public static void main(String[] args) {
        // 1. 使用文件流读取核心配置文件 SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        
        // 2. 创建 SqlSessionFactory 工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        
        // 3. 取出 SqlSession 对象
        SqlSession session = factory.openSession();

        // 4. 完成数据库操作,指定执行的操作:mapper 中指定的 ${namespace}.${id}
        List<User> list = session.selectList("user.getAllUser");

        // 5. 如果是增删改的操作需要手动提交事务,查询的时候不要提交事务。
        // sqlSession.commit();

        // 4. 关闭 SqlSession 对象
        session.close();
    }
}

动态代理的步骤

  1. UserMapper.xml 文件与 UserMapper.java 的接口必须同一目录下。
  2. UserMapper.xml 文件与 UserMapper.java 的接口文件名必须一致,除后缀名。
  3. UserMapper.xml 文件与 UserMapper.java 的接口中方法的名称完全一致。
  4. UserMapper.xml 文件中标签的 parameterType 属性值与 UserMapper.java 的接口中方法的参数值完全一致
  5. UserMapper.xml 文件中标签的 resultType 值与 UserMapper.java 接口文件中方法中的返回值类型完全一致
  6. UserMapper.xml 文件中 namespace 属性必须是接口的完全限定名称 com.lmb.mapper.UserMapper
  7. 在 SqlMapConfig.xml 文件中注册 mapper 文件时,使用 class=接口的完全限定名称。(class=com.lmb.mapper.UserMapper)

动态代理的 MyBatis 编程步骤

class Test {
    public static void main(String[] args) {
        // 1. 使用文件流读取核心配置文件 SqlMapConfig.xml
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        
        // 2. 创建 SqlSessionFactory 工厂
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        
        // 3. 取出 SqlSession 对象
        SqlSession session = factory.openSession();
        
        // 4. 取出动态代理的对象,完成接口方法的调用。
        UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

        // 4. 完成数据库操作。调用接口中方法,实际上是执行了 MyBatis 代理功能
        List<User> list = userMapper.getAll();

        // 5. 如果是增删改的操作需要手动提交事务,查询的时候不要提交事务。
        // sqlSession.commit();

        // 4. 关闭 SqlSession 对象
        session.close();
    }
}

Mapper 文件中标签

  1. :查询

  2. :插入

  3. :更新

  4. :删除

  5. select last_insert_id():返回主键

  • keyProperty:标签内容的运算值要放到哪个属性中
  • resultType:返回的类型
  • order:"BEFORE"/"AFTER" 在 sql 语句执行之后/之前返回

参数

  1. id:指定调用的名称
  2. parameterType:参数的数据类型
  3. resultType:返回值的类型

#{} 和 ${} 的使用区别

  1. ${} 可以拼接到 sql 语句的关键字中,存在 sql 注入的风险。
  • select ,{}, {} from t_user
  1. #{} 一般作为未知值的占位符,不存在 sql 注入的风险。
  • select name, age from t_user where id=#{}

动态 sql

  1. sql / include
  • 定义代码片段 / 引用代码片段
<mapper>
    <sql id="columns">
        id, userName, password, createTime, updateTime
    </sql>

    <select id="getAll" resultType="user">
        select <include refid="columns" />
        from <include refid="table" />
    </select>
</mapper>
  1. where
  • 替代 where 关键字
<mapper>
  <select>
      select <include refid="columns" />
      from <include refid="table" />
      <where>
          id = #{id}
      </where>
  </select>
  <!--
    select id, userName, password, createTime, updateTime
    from t_user
    where id = #{id}
  -->
</mapper>
  1. if
  • 条件判断为 true,将会把代码片段插入 sql 语句中
  • attribute
    • test:条件判断表达式
<!-- 按多种组合条件查询 -->
<mapper>
  <select id="getByConditions" parameterType="user" resultType="user">
      select <include refid="columns" />
      from <include refid="table" />
      <where>
          <if test="userName != null and userName != ''">
              and userName like concat("%", #{userName}, "%")
          </if>
          <if test="password != null and password != ''">
              and password like concat("%", #{password}, "%")
          </if>
      </where>
  </select>
</mapper>
  1. set
  • 代替 update 语句中的 set 关键字
<mapper>
  <update>
      update t_user
      <set>
          <if test="userName != null and userName != ''">
              userName = #{userName}
          </if>
          <if test="password != null and password != ''">
              password = #{password}
          </if>
      </set>
      where id = #{id}
  </update>
</mapper>
  1. foreach
  • 循环
  • attribute
    • collection:array/list/map 循环的数据类型(数组/集合/map)
    • item:每次循环的临时变量
    • separator:多个值或者对象或者sql语句之间的分隔符
    • open:整个循环的前置符号
    • close:整个循环的后置符号
<mapper>
    <!-- 批量增加 -->
    <insert id="insertBatch">
        insert into t_user(userName, password, createTime, updateTime)
        values
        <foreach collection="list" item="user" separator=",">
            (#{user.userName}, #{user.password}, #{user.createTime}, null)
        </foreach>
    </insert>
    
    <!-- 批量更新 -->
    <update id="updateBatch">
        <foreach collection="list" item="user" separator=";">
            update <include refid="table" />
            <set>
                <if test="user.userName != '' and user.userName != null">
                    userName=#{user.userName},
                </if>
                <if test="user.password != '' and user.password != null">
                    password=#{user.password},
                </if>
                <if test="user.createTime != '' and user.createTime != null">
                    createTime=#{user.createTime},
                </if>
                <if test="user.updateTime != '' and user.updateTime != null">
                    updateTime=#{user.updateTime},
                </if>
            </set>
            where id=#{user.id}
        </foreach>
    </update>
</mapper>

指定参数位置

如果入参是多个,可以通过指定参数位置进行传参。 如果一个操作需要包含多个参数,例如:查询指定时间范围内的并且指定年龄内的用户 int getUserByTimeAndAge(Date timeBegin, Date timeEnd,); 这份操作的参数有4个,

在 mapper.xml 文件中可以使用 #{arg0},#{arg1},#{arg2},#{arg3} 分别代表第一个到最后一个参数

总结入参和 mapper.xml 中引用的方式

  1. 单个入参
  • 入参为基础数据类型(int/String/char/boolean)
    • parameterType为对应的数据类型:int/String
    • 引用:#{value} {value},#{}和{}中可以随便写,最后引用的参数都是那个唯一的入参
  • 入参是实体类或者实体类的集合
    • parameterType为对应的实体类的全限定类名或者别名
    • 引用:#{userName} 代表实体类中的 userName 属性
  • 入参是一个map(推荐)
    • 使用 map 对所有的参数进行封装:map{ name: "Lee", age: 15, begin: "11" end: "22" }
    • parameterType不用写
    • 引用:#{name} 表示 map 中的 name 值
  1. 多个入参
  • 使用 @Param 的注解
    • parameterType不用写
    • UserMapper.java 接口类中:int getById(@Param("userId) int id);
    • 引用:#{userId}
  • 使用 arg 的方式引用
    • parameterType不用写
    • UserMapper.java 接口类中:int getById(int id, String name, int age);
    • 引用:#{arg0},#{arg1},...

总结返回值的情况

  1. 返回一个实体类或者实体类的集合
  • 查询单条数据或者批量查询
  1. 返回值是一个基础数据类型
  • resultType为对应的数据类型 int/String...
  • 例如:增删改之后返回一个 int 数代表修改的数据的数量,
  1. 返回值是一个 map或者 map 的集合
  • resultType:map
  • 用于没有实体类可以封装对应的数据时,故使用 map 来返回

实体类的成员变量和列名不一致怎么解决

User 实体类中的对用户主键的定义是 String id; t_user 表中对用户主键的列名定义 是 uid; 当这两个不一致的时候怎么处理

  1. 在 Mapper.xml 文件中使用别名,对在实体类中不一样的列名起对应的别名
<mapper>
    <select>
        select uid id form t_user where id = 3;
    </select>
</mapper>
  1. 使用 resultMap 映射处理
  • attribute
    • id:resultMap 的 id,在 sql 标签中使用
    • type:映射到的实体类
  • resultMap 下的标签
    • id:主键绑定
    • result:非主键绑定
      • property:实体类中的成员变量名称
      • column:sql 语句中查询结果中的列名
    • collection:集合绑定
      • property:实体类中的成员变量名称
      • ofType:集合中泛型对应的实体类(全限定类名/别名)
      • column:sql 标签中引用作为参数,传递给下一条 sql 语句
      • select:指定一条定义好的查询语句,使用 column 提供的参数做做查询,将查询的结果填充到 property 指定的成员变量中
    • association:绑定实体类中成员类的变量
      • property:实体类中成员类的名称
      • javaType:对应实体类的全限定类名或者别名
<mapper>
    <!-- 使用 resultMap 手动定义映射内容 -->
    <!--
        id:映射关系得 id
        type:映射到哪个实体类

        主键绑定:id 标签
        非主键绑定:result 标签

        property:实体类中的成员变量
        column:数据表中的列名
    -->
    <resultMap id="dusermap" type="duser">
        <id property="uid" column="id" />
        <result property="userName" column="userName" />
        <result property="ps" column="password" />
        <result property="createTime" column="createTime" />
        <result property="updateTime" column="updateTime" />
    </resultMap>

    <select id="getAll" resultMap="dusermap">
        select id, userName,password, createTime, updateTime
        from t_user;
    </select>
</mapper>

表之间的关联关系

  1. 一对多关联 一个老师教学一个班级的学生
  2. 多对一关联 一个班级的学生由一个老师来教学
  3. 一对一关联 一个家教老师只辅导一个学生,一个学生只请了一位家教老师
  4. 多对多关联 园区的车位和园区内的每一辆车,每一个车位可以停任意一辆车,任意一辆车可以停在任意一个车位上。

一对多的关联关系

一. 一个用户对应有多个文件夹。查询一个用户时,需要把用户的文件夹也查询返回。 sql语句的实现: select u.id, u.userName, f.id fid, f.fname, f.descr, f.createTime, f.updateTime from t_user u inner join t_folder f on u.id=f.userId where u.id = 1;

  1. 封装 t_folder 对应的实体类 Folder
  2. 重新构建用户实体类 User,加上一个 Folder 集合的属性表示用户的文件夹集合。
  3. 在 UserMapper.java 接口文件中定义方法。
  4. 在 UserMapper.xml 文件中先定义映射关系,再定义 语句 二. 重点是使用 resultMap 来定义实体类和 sql 意图据查询出来的结果的映射关系。 三. 总结来说,无论是什么关联关系,如果某方持有另一方的集合,则使用 标签来完成映射,如果某方持有另一方的对象,则使用 来完成映射。 事务 在 MyBatis 中设置事务的管理方式,在 SqlMapperConfig.xml 中使用 来设置程序员自己控制事务的提交和回滚。 也可以设置自动提交。在获取 sqlSession 对象时 sqlSession = factory.openSession():不传参数或者传 false 为手工提交事务,在增删改之后需要执行 sqlSession.commit() sqlSession = factory.openSession(true):为自动提交,在增删改之后不需要执行 sqlSession。commit() 缓存 什么是缓存 MyBatis提供两级缓存,一级缓存和耳机缓存,默认开启一级缓存。 缓存就是为了提高查询的效率,缓存内存的访问速度会大大快于访问数据库的速度 使用缓存之后的数据访问流程 未开启缓存 客户端 -> 服务端 -> 数据库 数据库查找完成之后返回服务端,再返回客户端 开起缓存机制 客户端 -> 服务端 -> 缓存 -> 数据库 服务端会先在缓存区内查找有没有目标数据有没有在缓存区内。 有直接获取返回 没有再去数据库中查找,数据库查找完毕之后会放一份在缓存区中,下次再查找时就可以从缓冲区中直接获取。 如果修改了数据库中的数据(commit),为了防止缓存中数据跟数据库中的不一致,会直接清空缓存区的全部内容 一级缓存使用的 sqlSession 的作用域,同一个 sqlSession 共享一级缓存的shuju 二级缓存使用的时 mapper 的作用域,不同的 sqlSession 只要访问的时同一个 mapper.xml 文件,则共享二级缓存的作用域。 什么是 ORM ORM(Object Relation Mapping) 对象关系映射 Mybatis 是非常优秀的 ORM 框架 将所有的操作都映射成一个对象来操作,将数据表中的数据映射成某个实体类。通过实体类来操作数据的获取存储使用等过程。 持久化操作:将对象保存到关系型数据库中,将关系型数据库中的数据读取取出来以对象的形式封装 MyBatis 就是一个非常优秀的持久化操作框架。 总结 三层架构 SSM框架(Spring/SpringMVC/MyBatis) 访问控制 动态代理 SqlMapperConfig 优化 批量注册别名 设置日志输出 批量注册 Mapper 文件 #{}和${}占位符的区别 #{} 是占位符 ${} 本质是字符串的替换 返回主键信息 动态 sql