Mybatis学习笔记总结

一、Mybatis概述

Mybatis是一款优秀的持久层框架。
支持自定义SQL、存储过程以及高级映射。
免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。
可以通过简单的XML或者注解来配置和映射原始类型、接口和Java POJO(Plain Old Java Object,普通老式Java对象)为数据库中的记录。

二、项目构建

Mybatis的依赖需自行在Meven仓库下载。
1.编写Mybatis的核心配置文件
每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的,Mybatis核心配置文件就是来用帮助我们构建SqlSessionFactory的。

<?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>
    <!--【外部资源】
        -引入配置连接字段的文件 db.properties
    -->
    <properties resource="db.properties"/>

    <!--【设置】
        -添加日志文件,查看执行的SQL语句信息
            logImpl = STDOUT_LOGGING (Mybatis自带标准日志功能)
            logImpl = log4j (自定义日志功能,需要编写log4j.properties配置文件,灵活多变,功能更加强大。)
            Mybatis官网日志配置 https://mybatis.org/mybatis-3/zh/logging.html
        -开启驼峰命名自动映射
            设置为true,可以将数据库经典下划线命名字段a_name对应实体类以驼峰命名的属性aName。
    -->
    <settings>
<!--        <setting name="logImpl" value="log4j"/>-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>

    <!--【别名】使用缩写词来代替冗余的类全限定名,仅用于XML文件中。
        -typeAlias元素给个别类添加别名
        -package元素给指定包下的类添加别名,默认为首字母小写的类名。也可在类的上方添加@Alias注解自定别名。
    -->
    <typeAliases>
<!--        <typeAlias alias="emp" type="com.example.pojo.Employees"/>-->
        <package name="com.example.pojo"/>
    </typeAliases>

    <!--【环境】
        -environments元素default属性指定当前使用的环境
        -environment元素可以有多个。
           默认使用JDBC的事务管理器。
           配置数据库连接池,引用外部配置文件的连接字段。
    -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${db.driver}"/>
                <property name="url" value="${db.url}"/>
                <property name="username" value="${db.username}"/>
                <property name="password" value="${db.password}"/>
            </dataSource>
        </environment>
    </environments>


    <!--【Mapper映射器注册】
        方式一(推荐):使用resource属性绑定xml文件,路径使用././格式。(缺点:没有智能提示)
        方式二:使用class属性绑定接口文件。(要求在接口的同级目录下有同名的*Mapper.xml文件)
        方式三:使用package元素将指定包下的所有映射器接口全部注册为映射器。(要求与方式二相同)
    -->
    <mappers>
        <mapper resource="mybatis\mapper\EmpMapper.xml"/>
<!--        <mapper resource="mybatis/mapper/UserMapper.xml"/>-->
<!--        <mapper class="com.example.dao.UserMapper"/>-->
<!--        <package name="com.example.dao"/>-->
    </mappers>
</configuration>

2.加载核心配置文件,获取SqlSessionFactory实例。
SqlSessionFactory主要用于获取sqlSession的实例,sqlSession提供了数据库执行SQL命令所需的所有方法。

//SqlSessionFactory --> sqlSession
public class MybatisUtils {

    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //获取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();
    }
}

3.应用
第一步,编写接口类。

public interface UserMapper {
    List<User> getUserList();
}

第二步,编写对应的xml实现。

<?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命名空间要求对应一个Mapper接口-->
<mapper namespace="com.kuang.mapper.UserMapper">
    <select id="getUserList" resultType="com.kuang.pojo.User">
        select * from mybatis.user
    </select>
</mapper>

第三步,在核心配置文件中注册映射器。

<mappers>
    <mapper resource="com/kuang/mapper/UserMapper.xml"/>
</mappers>

最后一步测试

@Test
public void testMybatis(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    for (User user : mapper.getUserList()) {
        System.out.println(user);
    }
    sqlSession.close();
}

以下是使用Junit获取SqlSessionFactory的测试方法

public class MybatisTest {
    private static SqlSessionFactory sqlSessionFactory = null;

    private SqlSession sqlSession = null;
    private EmpMapper mapper;

    @BeforeClass
    public static void init() throws IOException {
        //1.利用流读取核心配置,加载数据源
        Reader reader = Resources.getResourceAsReader("mybatis-config.xml");

        //2.创建SqlSessionFactory构建器对象,该对象可以用来构建一个工厂类
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();

        //3.利用构建器创建SqlSessionFactory,需要引用核心文件
        sqlSessionFactory = builder.build(reader);
    }

    @Before
    public void before() throws IOException {
        //4.由工厂对象打开一个SqlSession
        sqlSession = sqlSessionFactory.openSession();
        mapper = sqlSession.getMapper(EmpMapper.class);
    }

    @After
    public void after(){
        sqlSession.close();
    }

    @Test
    public void t1(){
        int i = mapper.insertEmp(new Emp("小龙", "市场部", new Timestamp(new Date().getTime()), BigDecimal.valueOf(1500), "普通员工"));
        if(i > 0){
            sqlSession.commit();
            System.out.println("新增成功");
        }else{
            System.out.println("新增失败");
        }
    }

    @Test
    public void t2(){
        int i = mapper.updateEmpById(new Emp(1, "紫怡"));
        if(i > 0){
            sqlSession.commit();
            System.out.println("修改成功");
        }else{
            System.out.println("修改失败");
        }
    }

    @Test
    public void t3(){
        int i = mapper.deleteEmpById(29);
        if(i > 0){
            sqlSession.commit();
            System.out.println("删除成功");
        }else{
            System.out.println("删除失败");
        }
    }

    @Test
    public void t4(){
        Emp emp = mapper.selectEmpById(1);
        System.out.println(emp);
    }

    @Test
    public void t5(){
        List<Emp> emps = mapper.selectAll();
        for (Emp emp : emps) {
            System.out.println(emp);
        }
    }
}

三、使用注解进行简单语句的开发

我们可以使用Mybatis的注解开发一些简单映射语句,这样可以是代码显得更加简介。对于稍微复杂一点的语句Java 注解不仅力不从心,还会让你本就复杂的 SQL 语句更加混乱不堪。 因此,如果你需要做一些很复杂的操作,最好用 XML 来映射语句。
应用案例:

@Select("select * from \"ORDER\"")
List<Order> selectOrderList();

四、SQL语句构建器

Mybatis的SQL语句构建器能够帮助我们更加优雅的构建SQL语句
1.编写SQL语句构建器

//在此类中使用构造器,构造sql语句
public class SqlConstructor {
    //定义方法,返回查询的sql语句
    public String getSelectAll() {
        //使用内部类的形式构建
        return new SQL() {
            //这个大括号 -> {} 表示构造代码块,表示每一次创建此对象,都会执行构造代码块中的代码
            {
                //表示查询学生表中的左右记录
                SELECT("*");
                FROM("user");
            }
            //toString()方法表示将构造的sql语句转化为一个字符串,并返回
        }.toString();
    }
}

2.引用SQL语句

public interface UserMapper {
    @SelectProvider(type = SqlConstructor.class , method = "getSelectAll")
    public abstract List<User> selectAll();
}

五、缓存

什么是缓存?
缓存就是存储在内存中的临时数据。

缓存的作用?
可以有效的帮助我们提高查询的效率,减少系统开销,提高系统的效率。

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

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。
Mybatis中存在两种缓存机制:一级缓存与二级缓存

1、一级缓存
一级缓存是默认开启的。它只启用了本地的会话缓存,仅仅对一个会话中的数据进行缓存。
例,下方是一次会话中,进行的两次相同的查询。

@Test
public void test2(){
    //开启SqlSession会话
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
    mapper.getStudentById(1);
    mapper.getStudentById(1);
    //关闭会话
    sqlSession.close();
}

我们来查看输出日志
输出日志
通过查看日志我们可以看到,从Created connection 168366创建连接到Closing JDBC Conection …只进行了一次查询的操作。这是因为两次查询是相同的,第一次查询的时候Mybatis已经将结果缓存到内存中去了,当第二次进行相同的查询的时候,是从本地的缓存中直接读取数据,就不会再去访问数据库了。

一级缓存会在什么时候失效(刷新)
1.sqlSession关闭之后
2.执行增删改操作之后
3.sqlSession.clearCache()手动清理缓存

2、二级缓存
二级缓存也叫全局缓存,它的作用域在一个namespace命名空间内(mapper映射器)中。
工作机制:
一个会话查询一条数据,这个数据先回放到当前会话的一级缓存中;
如果当前会话关闭了,这个会话对应的一级缓存就没了,但是如果我们开启了二级缓存,那么这个会话的缓存将会保存到二级缓存中去;
新的会话查询,就可以从二级缓存中获取内容;
不同的mapper查出得数据会放在自己对应的缓存(map)中;

1.在mybatis的核心配合文件中添加设置,这一步是默认开启的,但是我们还是将它显示的写出来。

<settings>
    <!--全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。-->
    <setting name="cacheEnabled" value="true"/>
</settings>

2.在需要添加二级缓存的mapper.xml中添加

<cache/>

也可以自定义参数

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

用户查询的顺序如下图示:
在这里插入图片描述

3.自定义缓存
我们还可以功能更加强大的缓存。
依赖

<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.2.1</version>
</dependency>

Mapper配置

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!--
      maxElementsInMemory:缓存中最大允许创建的对象数
      maxInMemory:设定内存中创建对象的最大值。
      eternal:设置元素(译注:内存中对象)是否永久驻留。如果是,将忽略超时限制且元素永不消亡。
      timeToIdleSeconds:设置某个元素消亡前的停顿时间。
      timeToLiveSeconds:为元素设置消亡前的生存时间.
      overflowToDisk:设置当内存中缓存达到 maxInMemory 限制时元素是否可写到磁盘上。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      diskPersistent:重启时内存不持久化到硬盘。
     -->

    <diskStore path="java.io.tmpdir"/>
    <defaultCache maxElementsInMemory="10000" memoryStoreEvictionPolicy="LRU" eternal="false"
                  timeToIdleSeconds="300" timeToLiveSeconds="300" overflowToDisk="false" diskPersistent="false" />

    <cache name="districtDataCache"
           maxElementsInMemory="4000"
           eternal="true"
           overflowToDisk="false"
           diskPersistent="false"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

查看官方文档 Mybatis缓存

六、动态SQL

Mybatisj的动态SQL功能在于可以使用它的动态SQL元素根据不同条件拼接 SQL 语句。
Mybatis动态SQL官方文档
1.满足条件就加入(IF元素)

<if test="title != null">
	and title like = #{title}
</if>

2.多个条件中选其一(choose when otherwise元素)
choose要求与when一起使用。

<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>

3、动态插入where语句(where元素)
where元素下存在内容(语句)的才会插入where子句,且会删除子句开头的and或or。

<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>

4.用于in条件语句与批量操作的元素(Foreach元素)
collection属性值为遍历的集合名称,传入的参数如果没有使用@Param定义的话。默认的名称都是list。
item为迭代的元素。
index为迭代的元素下标。
open定义在语句开始前加入的字符。
close定义在语句结束后加入的字符。
separator定义插入迭代参数item之间的分隔符。
foreach可以迭代的元素有List、Set、Map、数组。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。

  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>

6.用于在语句前后添加/删除前缀后缀(trim)
prefix 给语句添加前缀
prefixOverrides 删除语句的前缀
suffix 给语句添加后缀
suffixOverrides 删除语句的后缀

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

7.用于动态插入(set元素)
set元素会去除语句后面多余的逗号。

<set>
  <if test="username != null">username=#{username},</if>
  <if test="password != null">password=#{password},</if>
  <if test="bio != null">bio=#{bio}</if>
</set>

8.用于复用的SQL语句(SQL片段)

<select id="getTeacherByIF" resultType="teacher" parameterType="map">
    select * from teacher
    where 1 = 1
    <include refid="if-id-name"></include>
</select>
<sql id="if-id-name">
    <if test="id != null">
        and id = #{id}
    </if>
    <if test="name != null">
        and name = #{name}
    </if>
</sql>

七、结果集映射及一对多多对一的关系处理案例

MyBatis 创建时的一个思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序,那就太好了,但可惜也没有。 MyBatis的ResultMap作用就是为了解决数据库库字段名与实体类属性名不同的问题

因为官方的解释有些地方比较生涩难懂,这里就我的个人实践在结合实例对Mybatis的高级结果集映射做一个更加简单易懂的讲解。附官方文档:Mybatis结果集映射

一、(案例)处理老师与学生一对多的结果集映射
注意这里不提供测试,只做讲解。请自行进行测试。

首先分析老师与学生的关系是一个老师对应多个学生,即一个老师可能教多个学生。在数据库中两者关系是使用外键关联的,我们先来建立两个表的实体类,如下:

//学生类
public class Student {
  private Integer id;
  private String name;
}
//老师类
public class Teacher {
  private Integer id;
  private String name;
}

实体类建好了,现在如果我们需要查询一个老师的信息及这个老师教的学生们。这时候就发现问题了,这里的查询需要进行连表,且查询的结果集我们如何进行封装,这时候我们就会疑惑了。怎么做呢?我们可以在老师类中添加一个学生类的集合。

public class Teacher {
  private Integer id;
  private String name;
    //一个老师对应多个学生
  private List<Student> students;
}

那么如何将结果集映射到这个实体类中,就可以使用Mybatis的结果集映射。省略接口与方法的定义,这里只讲映射器的实现。使用结果集映射的子查询方式查询老师的详细信息,代码如下:

<!--
属性:
reslutMap 用于指定映射器
-->
<select id="getTeacherById" resultMap="TeacherMap">
    select * from teacher where id = #{id}
</select>
<!--
标签:
resultMap	定义一个映射器
result	映射普通结果集
collection	映射一个集合
属性:
id 指定映射器名称
type 映射器映射的类型
property 要映射的属性
column 数据库字段
select 指定一个select映射
ofType 指定集合的泛型(即集合装的对象)
javaType 指定对象的Java类型
-->
<resultMap id="TeacherMap" type="teacher">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="students" select="getStudentById" column="id" ofType="student">
        <result property="id" column="id"/>
        <result property="name" column="name"/>
    </collection>
</resultMap>
<!--根据老师id查询学生-->
<select id="getStudentById" resultType="student">
    select * from student where tid = #{id}
</select>

这里使用了子查询的实现方式,也可以直接使用连表查询直接映射到结果集中的方式。

二、(案例)处理多对多关系的结果集映射
实际生活的老师与学生的关系是怎么样的,看如下对象关系的分析图。

老师学生
1N
N1

上图中将老师一列的数值相乘结果为N,同学一列的数值相乘结果同样是N。他们的关系即是多对多的关系。这种方法有个口诀“上下相乘”。

1.建立学生与老师的实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Teacher {
  private Integer id;
  private String name;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
  private Integer id;
  private String name;
  private Integer tid;
  //一个学生对应一个老师
  private Teacher teacher;
}

2.定义StudentMapper接口

public interface StudentMapper {
    public Student getStudent();

    public Teacher getTeacher(int id);
}

编写xml实现,这里提供了两种实现方式,子查询与连表的方式。一定不要忘记注册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命名空间要求对应一个Mapper接口-->
<mapper namespace="com.kuang.mapper.StudentMapper">

    <!--方式一:子查询(嵌套查询,SQL语句较简单,结果集映射较为复杂)-->
<!--    <select id="getStudent" resultMap="StudentTeacher">-->
<!--        select * from student-->
<!--    </select>-->
<!--    <select id="getTeacher" resultType="teacher">-->
<!--        select * from teacher where id = #{id}-->
<!--    </select>-->
<!--    <resultMap id="StudentTeacher" type="student">-->
<!--        <result property="id" column="id"/>-->
<!--        <result property="name" column="name"/>-->
<!--        &lt;!&ndash;复杂的属性,我们需要单独处理对象; association 集合; collection&ndash;&gt;-->
<!--        <association property="teacher" column="tid" javaType="teacher" select="getTeacher"/>-->
<!--    </resultMap>-->

    <!--方式二:连表查询(SQL语句麻烦,编写结果集映射更加简单)-->
    <select id="getStudent" resultMap="StudentTeacher">
        select s.id sid, s.name sname,t.id tid ,t.name tname from student s,teacher t where s.tid = t.id
    </select>
    <resultMap id="StudentTeacher" type="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="teacher">
            <result property="name" column="tname"/>
            <result property="id" column="tid"/>
        </association>
    </resultMap>
</mapper>

解决使用xml文件编写SQL语句时特殊字符插入的问题

1、xml文档关键字插入
Mybatis将SQL语句与程序分离,改为使用xml文件来编写我们的SQL语句。但有时我们在编写语句时由于xml的语法格式问题导致有些特殊字符无法使用。如<>号这个在xml文档中被认为是元素的开闭标签。这时候我们可以使用转义字符来代替这些特殊字符。
在这里插入图片描述
2、SQL语句通配符插入
在进行模糊查询的数据库操作中一般会存在一些像%、_、[]这类的通配符,这些通配符会被数据库所转义。如果我们不希望将这些通配符被转义,而是被数据库看为是普通字符,可以使用Escape关键字。

# '&M%'中M后面的%将被看做一个普通字符%
LIKE '%M%' ESCAPE 'M' 

Escape关键字的作用是将指定字符后的通配符看为是一个普通字符。

Mysql数据库SQl语句应用

1、mysql中进行表字段的模糊查询。

<select id="selectEmpList" resultType="emp" parameterType="map">
    select * from emp
    <where>
        isusable = 0
        <if test="empname!=null">
            and name like concat('%',#{empname},'%') escape '/'
        </if>
        <if test="empphone!=null">
            and phone like concat('%',#{empphone},'%') escape '/'
        </if>
    </where>
</select>

Oracle数据库SQL语句应用

1、在Oracle数据库中进行表数据新增。
已知Oracle中的主键字段需要使用序列来生成,那么新增一条语句之间就必须先使用selectKey标签获得主键,将其封装到实体类的主键字段中,在进行新增操作。
正确的新增语句如下:

<insert id="insertCommodity">
    <selectKey resultType="int" keyProperty="cid" order="BEFORE">
        select commodity_sq.nextval from dual
    </selectKey>
    insert into commodity(cid,cname,integral,state,price) values (#{cid},#{cname},#{integral},#{state},#{price})
</insert>

2、在Oracle数据库中进行模糊查询。
在Oracle查询语句中concat一次只能连接两个字符串。

<select id="selectCommodityByNameOrIntegral" resultType="com.example.pojo.Commodity">
     select * from commodity
     <where>
         <if test="cname != null">
             CNAME like concat(CONCAT('%',#{cname}),'%')
         </if>
     </where>
 </select>

3、Oracle数据库进行批量插入

<insert id="insertOrderDetail">
    insert into ORDER(ORDER_ID,MONEY)
    select order_sq.nextval,A.* from (
    <foreach collection="list" item="od" separator="union all" >
        select #{od.money} from dual
    </foreach>
    ) A
</insert>

SQL语句亲测

select ORDER_SQ.nextval,A.* from (
    select 1 from dual union all
    select 2 from dual union all
    select 3 from dual
) A;

结果会生成一张3行2列的临时表,且序列会自增。
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值