mybatis 超全笔记整理奉上

注:mybatis的学习和使用应依托于官方文档

环境搭建

  • 创建一个maven项目
  • 删除src目录
  • 导入maven依赖
<dependencies>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.22</version>
    </dependency>

    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.2</version>
    </dependency>
    <!--Junit-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

创建一个模块

  • 创建一个普通的maven项目模块(子项目“mybatis-01”)
  • 编写核心配置文件 (在resource目录下建立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>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/database1?serverTimezone=GMT%2B8&amp;useUnicode=true&amp;characterEncoding=utf-8"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>


    <mappers>
        <mapper resource="com/tomatoin/dao/JobMapper.xml"/>
    </mappers>

</configuration>
  • java包下建立com.tomatoin. dao / pojo / utils
  • 编写工具类
    mybatisUtils,这个工具类就做两件事。把资源加载进来;创建一个能执行sql的对象(这个sqlsession对象和jdbc的connection对象类似,有了他之后才能做后面一切关于sql的事情)
package com.tomatoin.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;
    static {
        //获取SqlSessionFactory对象
        InputStream inputStream = null;
        try {
            String resource = "mybatis-config.xml";
            inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //创建session实例,sqlSession完全包含了面向数据库执行sql命令的所有方法
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

编写代码

  • 实体类 pojo
  • 接口 dao
  • 接口实现类 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">
<!--namespace中绑定的是一个dao接口,以全类名显示-->
<mapper namespace="com.tomatoin.dao.JobDao">
    <!--select标签中的id是接口中对应的方法名-->
    <!--select标签中的resultType是返回类型,这里是Job实体类类型-->
    <select id="getJobList" resultType="com.tomatoin.pojo.Job">
        select * from database1.job;
    </select>
</mapper>

注意:每一个mapper.xml都需要在核心配置文件中注册,也就是上面的

方法一:

<mappers>
    <mapper resource="com/tomatoin/dao/JobMapper.xml"/>
</mappers>

方法二:

<mappers>
    <mapper resource="com.tomatoin.dao.JobMapper.xml"/>
</mappers>

方法二注意点:

  • 接口和这个mapper必须同名
  • 接口和这个mapper必须在同一包下

测试

在test目录下建立与上面相对应的结构 com.tomatoin.dao.JobDaoTest

package com.tomatoin.dao;

import com.tomatoin.pojo.Job;
import com.tomatoin.utils.mybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.List;

public class JobDaoTest {
    @Test
    public void test(){

        //先获取一切事情得以发生的起源————session对象
        SqlSession sqlSession = mybatisUtils.getSqlSession();

        //要执行方法,方法在接口里,所以先拿到接口
        JobDao mapper = sqlSession.getMapper(JobDao.class);

        //用拿到的接口对象执行语句
        List<Job> jobList = mapper.getJobList();

        //打印结果
        for (Job job : jobList) {
            System.out.println(job);
        }

        //关闭资源
        sqlSession.close();

    }
}

注意:如果出现mapper不存在,资源无法导出问题。在每个项目开始都要有build配置。

<build>
    <resources>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>

        <resource>
            <directory>src/main/resources</directory>
            <includes>
                <include>**/*.xml</include>
                <include>**/*.properties</include>
            </includes>
        </resource>
    </resources>
</build>

可能遇到的问题总结:

  • 配置文件没有注册
  • 绑定接口错误
  • 方法名不对
  • 返回类型不对
  • Maven资源导出问题

CRUD

每次编写只需要写接口、mapper、测试代码

1.namespace

namespace中的包名要和接口的包名一致

2.select

  • id: 对应的namespace中的方法名
  • resultType:sql语句执行的返回值
  • parameterType:参数名

3.insert

<insert id="addJob" parameterType="com.tomatoin.pojo.Job">
    insert into database1.job (id,JName,description)
    values (#{id},#{JName},#{description});
</insert>

4.update

<update id="updateJob" parameterType="com.tomatoin.pojo.Job">
    update database1.job
    set JName = #{JName},description = #{description}
    where id = #{id};
</update>

5.delete

<delete id="deleteJob" parameterType="int">
    delete from database1.job where id = #{id};
</delete>

别名

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

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
  <typeAlias alias="Comment" type="domain.blog.Comment"/>
  <typeAlias alias="Post" type="domain.blog.Post"/>
  <typeAlias alias="Section" type="domain.blog.Section"/>
  <typeAlias alias="Tag" type="domain.blog.Tag"/>
</typeAliases>

生命周期

理解我们之前讨论过的不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

提示 对象生命周期和依赖注入框架

依赖注入框架可以创建线程安全的、基于事务的 SqlSession 和映射器,并将它们直接注入到你的 bean 中,因此可以直接忽略它们的生命周期。 如果对如何通过依赖注入框架使用 MyBatis 感兴趣,可以研究一下 MyBatis-Spring 或 MyBatis-Guice 两个子项目。

SqlSessionFactoryBuilder
这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。 因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。 你可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但最好还是不要一直保留着它,以保证所有的 XML 解析资源可以被释放给更重要的事情。

SqlSessionFactory
SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。 也绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。 如果你现在正在使用一种 Web 框架,考虑将 SqlSession 放在一个和 HTTP 请求相似的作用域中。 换句话说,每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它。 这个关闭操作很重要,为了确保每次都能执行关闭操作,你应该把这个关闭操作放到 finally 块中。

ResultMap 结果集映射

  • 解决属性名和字段名不一致的问题
<resultMap id="下面取的名字name" type="com.tomatoin.pojo.Job">
    <result column="数据库中的字段名1" property="实体类中的名字1"/>
    <result column="数据库中的字段名2" property="实体类中的名字2"/>
    <result column="数据库中的字段名3" property="实体类中的名字3"/>
    <!--注:只需要写需要映射的字段即可-->
</resultMap>

<select id="getJobList" resultMap="起一个名字name">
    select * from database1.job;
</select>

日志

日志工厂

  • LOG4j
  • STDOUT_LOGGING
<configuration>
  <settings>
    ...
    <setting name="logImpl" value="LOG4J"/>
    ...
  </settings>
</configuration>

分页

  • 减少数据的处理量

使用limit分页

select * from database1.job limit #{startIndex},#{pageSize};

注解

1.CRUD

使用注解不需要再配置mapper.xml文件,直接在接口的方法上标明相关方法即可,与此同时,mapper中也不再需要配置xml文件而是配置接口。

  • @Insert()
  • @Select()
  • @Update()
  • @Delete()

只以Select方法为例:

接口中:

@Select("select * from Job")
List<Job> getJobList();

注:两种sql语句最好不要同时使用;接口需要在mapper中配置;增删改事务是需要提交的,可以直接在mybatisUtils中将openSession方法的参数设为true(自动提交)

public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);
}

联表查询 ~ 多对一

有学生和老师两张表(多个老师对应一个学生),学生中有一个名为tid的外键,与老师表进行联合。
现进行联表查询,查出每个学生对应的老师的名字和该学生的姓名和id。

sql:

SELECT
    s.id sid,
    s.NAME sname,
    t.NAME tname
FROM
    student s,
    teacher t
WHERE
    s.tid = t.id;

在创建实体类时,如果里面有外键元素,外键对应的实体类应为所关联的类类型,因此会出现实体类名与数据库元素名不相符的情况,所以使用映射。

注:这里查询时被起别名的数据在mapper中也应该用别名做映射。

逻辑关系:

首先要对Student类做映射,把里面的id , name , teacher 一一映射,但是teacher本身又是一个复杂类型(类类型),下面进行联表映射操作:对teacher类进行association的声明,告诉计算机现在处理一个“联合”,与之联合的属性叫teacher,类型是Teacher,对这个类中的name属性进行映射以此来获得复杂类型中的元素。

<mapper namespace="com.tomatoin.dao.StudentDao">
    <!--select标签中的id是接口中对应的方法名-->
    <!--select标签中的resultType是返回类型,这里是Job实体类类型-->
    <select id="getStudentList" resultMap="StudentTeacher">
        SELECT
            s.id sid,
            s.NAME sname,
            t.NAME tname
        FROM
            student s,
            teacher t
        WHERE
            s.tid = t.id;
    </select>
    <resultMap id="StudentTeacher" type="com.tomatoin.pojo.Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="com.tomatoin.pojo.Teacher">
            <result property="name" column="tname"/>
        </association>
    </resultMap>
</mapper>

编写测试类,运行结果如下(id没有被映射所以为):

Student{id=1, name='小明', teacher=Teacher{id=0, name='秦老师'}}
Student{id=2, name='小红', teacher=Teacher{id=0, name='秦老师'}}
Student{id=3, name='小张', teacher=Teacher{id=0, name='秦老师'}}
Student{id=4, name='小李', teacher=Teacher{id=0, name='秦老师'}}
Student{id=5, name='小王', teacher=Teacher{id=0, name='秦老师'}}

联表查询 ~ 一对多

teacher类中加入联合变量:students
package com.tomatoin.pojo;

import java.util.List;

public class Teacher {
    private int id;
    private String name;

    private List<Student> students;

    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", students=" + students +
                '}';
    }
}

sql:

SELECT
    t.id tid,
    t.NAME tname,
    s.id sid,
    s.tid stid,
    s.NAME sname
FROM
    student s,
    teacher t
WHERE
    s.tid = t.id
  AND t.id = #{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">
<!--namespace中绑定的是一个dao接口,以全类名显示-->
<mapper namespace="com.tomatoin.dao.TeacherDao">
    <!--select标签中的id是接口中对应的方法名-->
    <!--select标签中的resultType是返回类型,这里是Job实体类类型-->
    <select id="getTeacher" resultMap="TeacherStudents">
        SELECT
            t.id tid,
            t.NAME tname,
            s.id sid,
            s.tid stid,
            s.NAME sname
        FROM
            student s,
            teacher t
        WHERE
            s.tid = t.id
          AND t.id = #{id};
    </select>
    <resultMap id="TeacherStudents" type="com.tomatoin.pojo.Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <collection property="students" ofType="com.tomatoin.pojo.Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="stid"/>
        </collection>
    </resultMap>
</mapper>

这里由于要查出的是一个 类的列表 所以不能再使用javaType,转而使用ofType规定类型。

test:

package com.tomatoin.dao;

import com.tomatoin.pojo.Job;
import com.tomatoin.pojo.Student;
import com.tomatoin.pojo.Teacher;
import com.tomatoin.utils.mybatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.HashMap;
import java.util.List;

public class JobDaoTest {
    @Test
    public void test1(){
        SqlSession sqlSession = mybatisUtils.getSqlSession();
        TeacherDao mapper = sqlSession.getMapper(TeacherDao.class);
        Teacher teacher = mapper.getTeacher(1);
        System.out.println(teacher);
    }
}

结果:

Teacher{id=1, name='秦老师', students=
[Student{id=1, name='小明', tid=1}, 
Student{id=2, name='小红', tid=1}, 
Student{id=3, name='小张', tid=1}, 
Student{id=4, name='小李', tid=1}, 
Student{id=5, name='小王', tid=1}]}

动态SQL

引自官方源文档翻译:

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。
使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。
如果你之前用过 JSTL 或任何基于类 XML 语言的文本处理器,你对动态 SQL 元素可能会感觉似曾相识。在 MyBatis 之前的版本中,需要花时间了解大量的元素。借助功能强大的基于 OGNL 的表达式,MyBatis 3 替换了之前的大部分元素,大大精简了元素种类,现在要学习的元素种类比原来的一半还要少。

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

if
使用动态 SQL 最常见情景是根据条件包含 where 子句的一部分。比如:

<select id="findActiveBlogWithTitleLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
</select>

这条语句提供了可选的查找文本功能。如果不传入 “title”,那么所有处于 “ACTIVE” 状态的 BLOG 都会返回;如果传入了 “title” 参数,那么就会对 “title” 一列进行模糊查找并返回对应的 BLOG 结果(细心的读者可能会发现,“title” 的参数值需要包含查找掩码或通配符字符)。
如果希望通过 “title” 和 “author” 两个参数进行可选搜索该怎么办呢?首先,我想先将语句名称修改成更名副其实的名称;接下来,只需要加入另一个条件即可。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

choose、when、otherwise

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。
还是上面的例子,但是策略变为:传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找的情形。若两者都没有传入,就返回标记为 featured 的 BLOG(这可能是管理员认为,与其返回大量的无意义随机 Blog,还不如返回一些由管理员精选的 Blog)。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

trim、where、set

前面几个例子已经方便地解决了一个臭名昭著的动态 SQL 问题。现在回到之前的 “if” 示例,这次我们将 “state = ‘ACTIVE’” 设置成动态条件,看看会发生什么。

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  WHERE
  <if test="state != null">
    state = #{state}
  </if>
  <if test="title != null">
    AND title like #{title}
  </if>
  <if test="author != null and author.name != null">
    AND author_name like #{author.name}
  </if>
</select>

如果没有匹配的条件会怎么样?最终这条 SQL 会变成这样:

SELECT * FROM BLOG
WHERE

这会导致查询失败。如果匹配的只是第二个条件又会怎样?这条 SQL 会是这样:

SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’

这个查询也会失败。这个问题不能简单地用条件元素来解决。这个问题是如此的难以解决,以至于解决过的人不会再想碰到这种问题。
MyBatis 有一个简单且适合大多数场景的解决办法。而在其他场景中,可以对其进行自定义以符合需求。而这,只需要一处简单的改动:

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG
  <where>
    <if test="state != null">
         state = #{state}
    </if>
    <if test="title != null">
        AND title like #{title}
    </if>
    <if test="author != null and author.name != null">
        AND author_name like #{author.name}
    </if>
  </where>
</select>

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。
如果 where 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。
用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。比如:

<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="email != null">email=#{email},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

这个例子中,set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。
来看看与 set 元素等价的自定义 trim 元素吧:

<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

注意,我们覆盖了后缀值设置,并且自定义了前缀值。

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历(尤其是在构建 IN 条件语句的时候)。比如:

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量。它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符。这个元素也不会错误地添加多余的分隔符,看它多智能!

提示 你可以将任何可迭代对象(如 List、Set 等)、Map 对象或者数组对象作为集合参数传递给 foreach。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

缓存

一级缓存

  • 一级缓存是默认缓存级别,对于查询过的数据都会先被放在一个固定的区域,如果下次再有相同的查询,无需再次执行查询,直接从先前的缓存区中取出即可。这和Java静态池的概念有些类似(创建过的静态元素都会被放到静态池中,再创建相同的对象时直接引用池子中的地址)

  • 一级缓存是在sqlsession区开辟的缓存空间。

二级缓存

使用标签

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

二级缓存的启用时间:

  • 当一级缓存失效时数据会进入二级缓存。
  • 在新的查询开始时,会先进入二级缓存查询,二级没有进一级。
  • 二级缓存是在mapper中开辟的缓存

提示 二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert/delete/update 语句时,缓存会获得更新。

笔记记自狂神说的mybatis视频,讲得十分清晰,强推 =#=

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值