MyBatis

发布时间 2023-12-13 11:09:56作者: 西芹-小汤圆

简介

特性

  1. MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架。
  2. MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
  3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和ava的POJO(Plain Old Java Objects,普通的Java对象)映射成数据库中的记录。
  4. MyBatis是一个半自动的ORM(Object Relation Mapping)框架。

与其他持久化层技术对比

JDBC

  • SQL夹杂在ava代码中耦合度高,导致硬编码内伤。
  • 维护不易且实际开发需求中SQL有变化,频繁修改的情况多见。
  • 代码冗长,开发效率低

Hibernate和JPA

  • 操作简便,开发效率高。
  • 程序中的长难复杂SQL需要绕过框架。
  • 内部自动生产的SQL,不容易做特殊优化。
  • 基于全映射的全自动框架,大量字段的POJO进行部分映射时比较因难。
  • 反射操作太多,导致数据库性能下降。

搭建MyBatis

在resources下创建目录的时候应该使用/分割,而不是使用.,否则创建出来的是假包。

以包为单位引入映射文件要求mapper接口所在的包要和映射文件所在的包一致且mapper接口要和映射文件的名字一致。

核心配置文件

<property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/>需要设置字符集,否则会出现错误。

<?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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"/>
                <property name="username" value="root"/>
                <property name="password" value="159123zxc"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/UserMapper.xml"/>
    </mappers>
</configuration>

创建mapper接口

MyBatis中的mapper接口相当于以前的dao。但是区别在于,mapper仅仅是接口,我们不需要提供实现类。

public interface UserMapper {

    int insertUser();

}

面向接口配置文件

MyBatis面向接口编程的两个一致,映射文件的namespace要和mapper接口的全类名一致;映射文件中的SQL语句的id要和mapper接口的中的方法名一致。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.atguigu.mybatis.mapper.UserMapper">
    <insert id="insertUser">
        insert into t_user values(null,'admin','1234560',23,'男','159123@qq.com')
    </insert>
</mapper>

测试

SqlSession:代表Java程序和数据库之间的对话。

注意在修改完成后需要提交,程序不会帮我们自动提交,也可以在openSession处设置参数为true自动提交。

查询功能的标签必须设置resultType设置默认的映射关系或resultMap设置自定义的映射关系。可以在配置文件中设置<typeAliases>标签设置类型别名,写起来更方便。,或者<package>标签以包为单位,将包下所有的类型设置默认的类型别名。

public class MyBatisTest {

    @Test
    public void testMyBatis() throws Exception {
        //加载核心配置文件
        InputStream is = Resources.getResourceAsStream("mybatis.xml");
        //获取SqlSessionFactoryBuilder
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(is);
        //获取SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //获取mapper接口对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //测试功能
        int result = mapper.insertUser();
        System.out.println("result:"+result);
        //提交事务
        sqlSession.commit();
    }

}

MyBatis获取参数值的两种方式

MyBatis获取参数值的两种方式:${}#{}${}的本质是字符串拼接,#{}的本质是占位符赋值。

${}使用字符串拼接的方式拼接sql,若为字符串类型或日期类型的字段进行赋值时,需要手动加单引号;但是#{}使用占位符赋值的方式拼接sql,此时为字符串类型或日期类型的字段进行赋值时,可以自动添加单引号。

获取参数

  1. mapper接口方法的参数为单个的字面量类型,可以通过${}#{}以任意的名称获取参数值,但是需要注意${}的单引号问题。
  2. mapper接口方法的参数为多个时,此时MyBatis会将这些参数放在一个map集合中,以两种方式进行存储:以arg0,arg1...为键,以参数为值;以param1,param2...为键,以参数为值。因此只需要通过#{}${}以键的方式访问值即可,但是需要注意${}的单引号问题。
  3. 若mapper接口方法的参数有多个时,可以手动将这些参数放在一个map中存储,只需要通过#{}${}以键的方式访问值即可,但是需要注意${}的单引号问题。
  4. mapper接口方法的参数是实体类类型的参数,只需要通过#{}${}以属性的方式访问属性值即可,但是需要注意${}的单引号问题。
  5. 使用@Param注解命名参数,User checkLoginByParam(@Param("username") String username, @Param("password") String password);,相当于第二种和第三种情况的结合。此时MyBatis会将这些参数放在一个map集合中,以两种方式进行存储:以@Param注解的值为键,以参数为值;以param1,param2...为键,以参数为值。

实例

public void testGetUserByUsername(){
    SqlSession sqlSession = SqlSessionUtils.getSqlSession();
    ParameterMapper mapper = sqlSession.getMapper(ParameterMapper.class);
    User user = mapper.getUserByUsername("admin");
    System.out.println(user);
}
<!--获取单个参数-->
<select id="getUserByUsername" resultType="User">
    select * from t_user where username = #{username}
</select>
<!--获取多个参数-->
<select id="checkLogin" resultType="User">
    select * from t_user where username = #{arg0} and password = #{arg1}
</select>

Sql语句

@MapKey

//将每条数据转换的Map集合作为值,以某个字段的值作为
@MapKey("id")
Map<String,Object> getAllUserToMap();

模糊查找

<!--使用${}的模糊查找-->
<select id="getUserByLike" resultType="User">
    select * from t_user where username like '%${username}%'
</select>
<!--使用#{}和concat的模糊查找-->
<select id="getUserByLike" resultType="User">
    select * from t_user where username like concat('%',#{username},'%')
</select>
<!--最常用的方式-->
<select id="getUserByLike" resultType="User">
    select * from t_user where username like "%"#{username}"%"
</select>

批量删除

注意#{}会自动添加单引号,因此使用${}

<delete id="deleteMore">
    delete from t_user where id in (${ids})
</delete>

动态设置表名

<select id="getUserByTableName" resultType="User">
    select * from ${tableName}
</select>

获取功能自增的主键

因为添加操作的返回值是固定的,因此将主键值作为对象的一个属性返回。

<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
    insert into t_user values(null,#{username},#{password},#{age},#{sex},#{email})
</insert>

常见问题

字段名和属性名不一致问题

在遇到属性值不一致的情况并不会报错,只是会忽略为不一致的属性赋值。

使用字段别名

<select id="getAllEmp" resultType="Emp">
    select eid,emp_name empName,age,sex,email from t_emp
</select>

全局设置转换

在MyBatis的配置文件中设置将下划线转换为驼峰命名法。

<settings>
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

使用resultmMap

<resultMap id="empResultMap" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="empName" column="emp_name"></result>
    <result property="age" column="age"></result>
    <result property="sex" column="sex"></result>
    <result property="email" column="email"></result>
</resultMap>

多对一映射问题

级联方式

<resultMap id="empAndDeptResultMap" type="Emp">
    <id property="eid" column="eid"></id>
    <result property="dept.did" column="did"></result>
    <result property="dept.deptName" column="dept_name"></result>
</resultMap>
<select id="getEmpAndDept" resultMap="empAndDeptResultMap">
    select * from t_emp left join t_dept on t_emp.did = t_dept.did where t_emp.eid = #{eid}
</select>

使用标签

<resultMap id="empAndDeptResultMapTwo" type="Emp">
    <id property="eid" column="eid"></id>
    <association property="dept" javaType="Dept">
        <id property="did" column="did"></id>
        <result property="deptName" column="dept_name"></result>
    </association>
</resultMap>

分步查询

首先查询出对应部门的did,然后再调用下一条Sql语句去查询dept信息。

分步查询的好处是延迟加载,第一步只会调用第一天Sql语句,只有在需要dept信息时才会执行第二条Sql语句。但是必须在核心配置文件中设置全局信息,设置lazyLoadingEnabled开启和aggressiveLazyLoading关闭。可以在属性中的fetchType设置单条语句的查询策略是立即加载还是延迟加载。

 <!-- EmpMapper.xml-->
 <resultMap id="empAndDeptByStepResultMap" type="Emp">
    <id property="eid" column="eid"></id>
     <!-- select:设置分步查询的Sql的唯一标识,column设置分步查询的条件-->
    <association property="dept"
                select="com.atguigu.mybatis.mapper.DeptMapper.getEmpAndDeptByStepTwo"
                column="did" fetchType="eager"></association>
</resultMap>
<!--    Emp getEmpAndDeptByStepOne(@Param("eid") Integer eid);-->
<select id="getEmpAndDeptByStepOne" resultMap="empAndDeptByStepResultMap">
    select * from t_emp where eid = #{eid}
</select>

 <!-- DeptMapper.xml-->
<select id="getEmpAndDeptByStepTwo" resultType="Dept">
    select * from t_dept where did = #{did}
</select>

一对多问题

collection标签

<resultMap id="deptAndEmpResultMap" type="Dept">
    <id property="did" column="did"></id>
    <result property="deptName" column="dept_name"></result>
    <collection property="emps" ofType="Emp">
        <id property="eid" column="eid"></id>
        <result property="empName" column="emp_name"></result>
        <result property="age" column="age"></result>
        <result property="sex" column="sex"></result>
        <result property="email" column="email"></result>
    </collection>
</resultMap>
<!--    Dept getDeptAndEmp(@Param("did") Integer did);-->
<select id="getDeptAndEmp" resultMap="deptAndEmpResultMap">
    select * from t_dept left join t_emp on t_dept.did = t_emp.did where t_dept.did = #{did}
</select>

分步查询

 <!-- DeptMapper.xml-->
<resultMap id="deptAndEmpByStepResultMap" type="Dept">
    <id property="did" column="did"></id>
    <result property="deptName" column="dept_name"></result>
    <collection property="emps"
                select="com.atguigu.mybatis.mapper.EmpMapper.getDeptAndEmpByStepTwo"
                column="did"></collection>
</resultMap>
<!--    Dept getDeptAndEmpByStepOne(@Param("did") Integer did);-->
<select id="getDeptAndEmpByStepOne" resultMap="deptAndEmpByStepResultMap">
    select * from t_dept where did = #{did}
</select>

 <!-- EmpMapper.xml-->
 <select id="getDeptAndEmpByStepTwo" resultType="Emp">
    select * from t_emp where did = #{did}
</select>

动态Sql

MyBatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。


if

根据标签中test属性所对应的表达式决定标签中的内容是否需要拼接到SQL。

<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp where 1=1
    <if test="empName != null and empName != ''">
        emp_name = #{empName}
    </if>
    <if test="age != null and age != ''">
        and age = #{age}
    </if>
    <if test="sex != null and sex != ''">
        and sex = #{sex}
    </if>
    <if test="email != null and email != ''">
        and email = #{email}
    </if>
</select>

where

where标签中有内容时,会自动生成where关键字,并且将内容前多余的and或or去掉。当where标签中没有内容时,此where标签没有任何效果。注意where标签不能将内容后面的多余的and和or去掉。

<select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <where>
        <if test="empName != null and empName != ''">
            emp_name = #{empName}
        </if>
        <if test="age != null and age != ''">
            and age = #{age}
        </if>
        <if test="sex != null and sex != ''">
            and sex = #{sex}
        </if>
        <if test="email != null and email != ''">
            and email = #{email}
        </if>
    </where>
</select>

trim

若标签中有内容时,prefix|suffix:将trim标签中内容前面或后面添加指定内容;suffixOverrides|prefixOverrides:将trim标签中内容前面或后面去掉指定内容。若标签没有内容时,trim标签没有任何效果。

 <select id="getEmpByCondition" resultType="Emp">
    select * from t_emp
    <trim prefix="where" suffixOverrides="and|or">
        <if test="empName != null and empName != ''">
            emp_name = #{empName} and
        </if>
        <if test="age != null and age != ''">
            age = #{age} and
        </if>
        <if test="sex != null and sex != ''">
            sex = #{sex} and
        </if>
        <if test="email != null and email != ''">
            email = #{email}
        </if>
    </trim>
</select>

choose、when、otherwise

choose、when、otherwise相当于if...else。

<select id="getEmpByChoose" resultType="Emp">
    select * from t_emp
    <where>
        <choose>
            <when test="empName != null and empName != ''">
                emp_name = #{empName}
            </when>
            <when test="age != null and age != ''">
                age = #{age}
            </when>
            <when test="sex != null and sex != ''">
                sex = #{sex}
            </when>
            <when test="email != null and email != ''">
                email = #{email}
            </when>
            <otherwise>
                did = 1
            </otherwise>
        </choose>
    </where>
</select>

foreach

foreach可以用于批量删除和批量添加。

<delete id="deleteMoreByArray">
    delete from t_emp where eid in
    <foreach collection="eids" item="eid" separator="," open="(" close=")">
        #{eid}
    </foreach>
</delete>
<!--第二种写法-->
<delete id="deleteMoreByArray">
    delete from t_emp where eid in
        <foreach collection="eids" item="eid" separator="or">
            eid = #{eid}
        </foreach>
</delete>
<!--批量添加-->
<insert id="insertMoreByList">
    insert into t_emp values
    <foreach collection="emps" item="emp" separator=",">
        (null,#{emp.empName},#{emp.age},#{emp.sex},#{emp.email},null)
    </foreach>
</insert>

Sql标签

将常用的Sql语句用Sql标签封装,需要使用时用include标签引用。

<sql id="empColumns">eid,emp_name,age,sex,email</sql>
<!--使用include标签引用-->
delete <include refid="empColumns"></include> from t_emp

缓存

一级缓存

一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问。

一级缓存默认开启。

一级缓存失效的四种情况

  1. 不同的SqlSession对应不同的一级缓存。
  2. 同一个SqlSession但是查询条件不同。
  3. 同一个SqlSession两次查询期间执行了任何一次增删改操作。
  4. 同一个SqlSession两次查询期间手动清空了缓存。

二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。

二级缓存需要手动开启。

使二级缓存失效的情况:两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效。

二级缓存开启

  1. 在核心配置文件中,设置全局配置属性cacheEnabled="true",默认为true,不需要设置。
  2. 在映射文件中设置标签<cache/>
  3. 二级缓存必须在SqlSession关闭或提交之后有效。
  4. 查询的数据所转换的实体类类型必须实现序列化的接口。

二级缓存配置

参数 作用
eviction 缓存回收策略
flushInterval 刷新间隔,单位毫秒
size 引用数目,代表缓存最多可以存储的对象
readOnly true是只读缓存;false是读写缓存

缓存查询顺序

  1. 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  2. 如果二级缓存没有命中,再查询一级缓存。
  3. 如果一级缓存也没有命中,则查询数据库。
  4. SqlSession关闭之后,一级缓存中的数据会写入二级缓存。

MyBatis的逆向工程

逆向工程:先创建数据库表,由框架负责根据数据库表,反向生成如下资源:Java实体类、Mapper接口和Mapper映射文件。

添加依赖和插件

<build>
      <!-- 构建过程中用到的插件 -->
      <plugins>

          <!-- 具体插件,逆向工程的操作是以构建过程中插件形式出现的 -->
          <plugin>
              <groupId>org.mybatis.generator</groupId>
              <artifactId>mybatis-generator-maven-plugin</artifactId>
              <version>1.3.0</version>

              <!-- 插件的依赖 -->
              <dependencies>

                  <!-- 逆向工程的核心依赖 -->
                  <dependency>
                      <groupId>org.mybatis.generator</groupId>
                      <artifactId>mybatis-generator-core</artifactId>
                      <version>1.3.2</version>
                  </dependency>

                  <!-- 数据库连接池 -->
                  <dependency>
                      <groupId>com.mchange</groupId>
                      <artifactId>c3p0</artifactId>
                      <version>0.9.2</version>
                  </dependency>

                  <!-- MySQL驱动 -->
                  <dependency>
                      <groupId>mysql</groupId>
                      <artifactId>mysql-connector-java</artifactId>
                      <version>5.1.8</version>
                  </dependency>
              </dependencies>
          </plugin>
      </plugins>
  </build>

创建逆向工程的配置文件

文件名必须是:generatorConfig.xml

设置完成后点击MAVEN->对应项目->Plugins->mybatis-generator->mybatis-generaator:generate。

生成后需要自行生成构造方法和toString方法。

<?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>
    <!--
            targetRuntime: 执行生成的逆向工程的版本
                    MyBatis3Simple: 生成基本的CRUD(清新简洁版)
                    MyBatis3: 生成带条件的CRUD(奢华尊享版)
     -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!-- 数据库的连接信息 -->
        <jdbcConnection driverClass="com.mysql.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf8"
                        userId="root"
                        password="123456">
        </jdbcConnection>
        <!-- javaBean的生成策略-->
        <javaModelGenerator targetPackage="com.atguigu.mybatis.pojo" targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
            <property name="trimStrings" value="true" />
        </javaModelGenerator>
        <!-- SQL映射文件的生成策略 -->
        <sqlMapGenerator targetPackage="com.atguigu.mybatis.mapper"  targetProject=".\src\main\resources">
            <property name="enableSubPackages" value="true" />
        </sqlMapGenerator>
        <!-- Mapper接口的生成策略 -->
        <javaClientGenerator type="XMLMAPPER" targetPackage="com.atguigu.mybatis.mapper"  targetProject=".\src\main\java">
            <property name="enableSubPackages" value="true" />
        </javaClientGenerator>
        <!-- 逆向分析的表 -->
        <!-- tableName设置为*号,可以对应所有表,此时不写domainObjectName -->
        <!-- domainObjectName属性指定生成出来的实体类的类名 -->
        <table tableName="t_emp" domainObjectName="Emp"/>
        <table tableName="t_dept" domainObjectName="Dept"/>
    </context>
</generatorConfiguration>

分页插件

插件依赖

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.2.0</version>
</dependency>

设置分页插件

mybatis-config.xml文件处设置插件。

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

使用分页

需要在查询功能之前开启分页,在查询功能之后获取分页相关信息。

public void testPageHelper(){
    try {
        InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(is);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        EmpMapper mapper = sqlSession.getMapper(EmpMapper.class);
        PageHelper.startPage(2, 4);
        List<Emp> list = mapper.selectByExample(null);
        list.forEach(emp -> System.out.println(emp));
    } catch (IOException e) {
        e.printStackTrace();
    }

}