mybatis-持久层框架(详细完整)

mybatis官方文档

一、mybatis简介

  1. MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射
  2. MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作
  3. MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO

二、特点

  1. 简单易学
  2. 灵活:mybatis不会对应用程序或者数据库的现有设计强加任何影响。 sql写在xml里,便于统一管理和优化。通过sql语句可以满足操作数据库的所有需求。
  3. 解除sql与程序代码的耦合:通过提供DAO层,将业务逻辑和数据访问逻辑分离。sql和代码的分离,提高了可维护性。
  4. 提供映射标签,支持对象与数据库的orm字段关系映射
  5. 提供对象关系映射标签,支持对象关系组建维护
  6. 支持编写动态sql

三、环境配置(入门)

步骤:

  • 导入依赖
  • 创建实体类
  • 创建全局mybatis-config.xml配置文件
  • 创建mapper interface,定义查询方法
  • 创建mapper.xml配置文件,使用mapper namespace映射mapper interface
  • 在mybatis-config.xml中映射绑定mapper.xml文件
  • 编写mybatisUtils工具类,用其获得sqlSession
  • 编写测试类,测试

1.导入mybatis依赖

<dependency>
     <groupId>org.mybatis</groupId>
     <artifactId>mybatis</artifactId>
     <version>3.4.6</version>
</dependency>

2.导入mysql数据库依赖

<!--引入mysql驱动-->
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>

3.导入junit测试依赖

<!--引入Junit4-->
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

4.dao层/mapper层

1.创建userMapper接口
package com.mybatis.mapper;
import com.mybatis.entity.User;
import java.util.List;
import java.util.Map;

public interface userMapper {
    List<User> getListUser();
}
2.创建userMapper.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">
<mapper namespace="com.mybatis.mapper.userMapper">
  <!--  <resultMap id="userRes" type="User">
        <result column="pwd" property="passWord"></result>
    </resultMap> -->
    <select id="getListUser" resultType="com.mybatis.entity.User">
        select * from user
    </select>
</mapper>

5.问题:

​ mapper.xml应放在资源目录的根目录下,如果要将其与接口放在一起,则需要配置maven项目的静态资源目录如下:

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

6.实体entity

package com.mybatis.entity;
public class User {
    private int Id;
    private String userName;
    private String passWord;
    public void setId(int id) {
        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 + '\'' +
                '}';
    }
}

7.创建utils

package com.mybatis.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;
public class MybatisUtils {
    private static SqlSessionFactory sqlSessionFactory;
    static {
        try{
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

8.创建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>
    <properties resource="db.properties" />
    <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>
    <mappers>
        <mapper resource="com/mybatis/mapper/userMapper.xml"/>
    </mappers>
</configuration>

9.创建db.properties文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.23.100:3306/mybatis
username=root
password=123456

10.创建test类

import com.mybatis.entity.User;
import com.mybatis.mapper.userMapper;
import com.mybatis.utils.MybatisUtils;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class mybatisTest {
    @Test
    public void test1(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        userMapper mapper = sqlSession.getMapper(userMapper.class);
        List<User> userList = mapper.getListUser();
        for (User user : userList) {
            System.out.println(user);
        }
        sqlSession.close();
    }
}

11.开启事务(CUID)

1.预编译

​ mybatis执行sql语句,会首先执行预编译,在进行CUID处理时,必须在执行sql语句后进行提交,否则数据库执行失败

 public void test4(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        userMapper mapper = sqlSession.getMapper(userMapper.class);
        mapper.setUser(user);
        sqlSession.commit();
        sqlSession.close();
    }
2.mybatis开启事务自动提交
public static SqlSession getSqlSession(){
    return sqlSessionFactory.openSession(true);
}

开启后执行事务后,会自动提交

四、基础

1.mybatis执行流程(简易)

在这里插入图片描述

2.属性名与列名不一致问题

​ 解决:在mapper.xml文件中,使用resultMap进行映射

<resultMap id="userRes" type="com.mybatis.entity.User">
    <result column="pwd" property="passWord"></result>
</resultMap>
<select id="getListUser" resultMap="userRes">
    select * from user
</select>
<!-- column  对应数据库中的字段,property 对应类中的属性->

3.类别名

​ 在mapper.xml文件中使用全类名比较麻烦,可在全局mybatis-config.xml中对类起别名

1.mybatis-config.xml
<typeAliases>
        <typeAlias type="com.mybatis.entity.User" alias="User"/>
</typeAliases>
2.mapper.xml
<resultMap id="userRes" type="User">
    <result column="pwd" property="passWord"></result>
</resultMap>
<select id="getListUser" resultMap="userRes">
    select * from user
</select>

4.resultMaps查询

1.mapper接口添加方法
package com.mybatis.mapper;
import com.mybatis.entity.User;
import java.util.List;
import java.util.Map;
public interface userMapper {
    List<User> getListUser();
    User getUser(Map map);
}
2.mapper.xml添加sql语句
  <select id="getUser" resultMap="userRes" parameterType="map">
        select * from user where id = #{id}
    </select>
<!--parameterType声明参数类型 -->
3.test类
@Test
public void test2(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    userMapper mapper = sqlSession.getMapper(userMapper.class);
    Map<String,Integer> map = new HashMap<String, Integer>();
    map.put("id",1);
    User user = mapper.getUser(map);
    System.out.println(user);
    sqlSession.close();
}

5.分页

  • mybatis分页的本质底层使用jdbc和mysql的limit实现;
  • mybatis本质是对jdbc的封装,比起其他orm框架更贴近底层更贴近
1.resultMap实现
1.mapper.xml添加select语句
 <select id="getListUser" resultMap="userRes" parameterType="map">
        <bind name="startPage" value="(startIndex-1)*pageSize"/>
        select * from user limit #{startPage},#{pageSize}
 </select>
2.mapper接口
public interface userMapper {
    //分页
    List<User> getListUser(Map map);
    //查询指定id
    User getUser(Map map);
}
3.test测试方法
@Test
public void test1(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    userMapper mapper = sqlSession.getMapper(userMapper.class);
    Map<String,Integer> map = new HashMap<String, Integer>();
    map.put("startIndex",2);
    map.put("pageSize",2);
    List<User> userList = mapper.getListUser(map);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}
2.ByRowBounds分页
1.mapper接口
package com.mybatis.mapper;
import com.mybatis.entity.User;
import java.util.List;
import java.util.Map;
public interface userMapper {
    //分页
    List<User> getListUser(Map map);
    //RowBounds分页
    List<User> getListByRowBounds();
    //查询指定id
    User getUser(Map map);
}
2.mapper.xml文件
<select id="getListByRowBounds" resultMap="userRes">
        select * from user
</select>
3.test方法
@Test
public void test3(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    RowBounds rowBounds = new RowBounds(2,2);
    List<User> userList = sqlSession.selectList("com.mybatis.mapper.userMapper.getListByRowBounds",null,rowBounds);
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

6.日志系统

1.STDOUT_LOGGING日志实现

mybatis内置标准日志

mybatis-config.xml

  <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
2.Log4j日志实现
1.mybatis-config.xml
<settings>
        <setting name="logImpl" value="Log4j"/>
    </settings>
2.导入log4j依赖
<!--log4j依赖-->
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
3.Log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

五、使用注解

1.为什么使用注解开发

​ 在以往的开发中需要配置大量的xml配置文件,尤其是SSM框架,为了简化开发,引入了注解开发

  • 还有另一种方法来完成语句映射。 它们映射的语句可以不用 XML 来配置,而可以使用 Java 注解来配置

  • 使用注解来映射简单语句会使代码显得更加简洁,但对于稍微复杂一点的语句,Java 注解不仅力不从心,需要做一些很复杂的操作,最好用 XML 来映射语句。

    ​ 在开发中注解和xml也可混合使用,在mybatis中使用xml和注解开发,可以由程序员自己自定义原生sql,这和habinate是不同的,这使得mybatis更具灵活性,但前提是开发者能够写出高效的sql

    面试重点:

    • mysql引擎
    • InnoDB底层原理
    • 索引
    • 索引优化

2.注解开发

1.绑定接口
<mappers>
    <mapper class="com.mybatis.mapper.userMapper"/>
    <mapper resource="com/mybatis/mapper/userMapper.xml"/>
</mappers>
<!-- 绑定接口,接口位置要在xml文件之前(也可能是两种文件名相同时,遵循此规则,否则报错)-->
2.mapper接口
    @Select("select * from user")
    List<User> getList();
3.测试
@Test
public void test4(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    userMapper mapper = sqlSession.getMapper(userMapper.class);
    List<User> userList = mapper.getList();
    for (User user : userList) {
        System.out.println(user);
    }
    sqlSession.close();
}

在单独使用注解开发时mapper.xml文件将不再需要

4.注解传参(增删改查)
1.修改mapper接口
    @Select("select * from user where id = #{id}")
    User getUser(int id);
2.修改测试类
@Test
public void test4(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    userMapper mapper = sqlSession.getMapper(userMapper.class);
    User use = mapper.getUser(1);
    System.out.println(user);
    sqlSession.close();
}
3.@Param注解使用
    @Select("select * from user where id = #{uid}")
    List<User> getList(@Param("uid") int id);

注意:此时sql中的参数#{uid},必须和@Param(“uid”)注解中的保持一致,否则报错

六.一对多

​ 在一对多查询时有两种方式:

  • 按查询嵌套处理
  • 按结果嵌套处理

1.环境准备:

1.student.java
package com.mybatis.entity;
public class Student {
    private int id;
    private String name;
    private String pwd;
    private Teacher teacher;
    public Student() {
    }
    public Student(int id, String name, String pwd, Teacher teacher) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
        this.teacher = teacher;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    public Teacher getTeacher() {
        return teacher;
    }
    public void setTeacher(Teacher teacher) {
        this.teacher = teacher;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                ", teacher=" + teacher +
                '}';
    }
}
2.teacher.java
package com.mybatis.entity;
public class Teacher {
    private int id;
    private String name;
    private String pwd;
    public Teacher() {
    }
    public Teacher(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}

2.按查询嵌套处理

<!--思路:
	1.查找student
    2.做结果集映射,嵌套teacher子查询
	3.mybatis会自动根据student表的外键取关联teacher
	4.#{uid}可以是任意值,最好使用uid
-->
<select id="getStudentList" resultMap="StudentTeacher">
    select * from student
</select>
<resultMap id="StudentTeacher" type="Student">
    <association property="teacher" column="uid" javaType="Teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="Teacher">
   select * from teacher where id = #{uid}
</select>

3.按结果嵌套处理

<!--
	思路:	
		1.首先根据sql去查询结果
		2.结果集嵌套映射
-->
<select id="getStudentList2" resultMap="StudentTeacher2">
    SELECT s.id sid,s.name sname,s.pwd spwd,t.id tid,t.name tname,t.pwd tpwd
    FROM teacher t join student s
    where t.id  = s.uid
</select>
<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <result property="pwd" column="spwd"/>
    <association property="teacher" javaType="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <result property="pwd" column="tpwd"/>
    </association>
</resultMap>

以上两种方式均依赖association,association标签来解决一对一的关联查询;
在这里插入图片描述
查询无误,则结果如上;

注意:如果关联查询的javabean中的属性名,数据库表中字段名一致,则需要做别名处理,即结果集映射,如上按结果嵌套处理代码;

七.多对一

1.环境准备

1.student.java
package com.mybatis.entity;
public class Student {
    private int id;
    private String name;
    private String pwd;
    private int tid;
    public Student() {
    }
    public Student(int id, String name, String pwd, int tid) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
        this.tid = tid;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    public int getTid() {
        return tid;
    }
    public void setTid(int tid) {
        this.tid = tid;
    }
    @Override
    public String toString() {
        return "Student{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                ", tid=" + tid +
                '}';
    }
}
2.teacher.java
package com.mybatis.entity;
import java.util.List;
public class Teacher {
    private int id;
    private String name;
    private String pwd;
    List<Student> students;
    public Teacher() {
    }
    public Teacher(int id, String name, String pwd, List<Student> students) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
        this.students = students;
    }
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getPwd() {
        return pwd;
    }
    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
    public List<Student> getStudents() {
        return students;
    }
    public void setStudents(List<Student> students) {
        this.students = students;
    }
    @Override
    public String toString() {
        return "Teacher{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                ", students=" + students +
                '}';
    }
}

1.按结果嵌套处理

<select id="getTeacher" resultMap="TeacherStudent">
    select t.id tid,t.name tname,t.pwd tpwd,s.id sid,s.name sname,s.pwd spwd,s.uid stid from teacher t
    join student s
    on t.id = s.uid
    where t.id = #{tid}
</select>
<resultMap id="TeacherStudent" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <result property="pwd" column="tpwd"/>
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="pwd" column="spwd"/>
        <result property="tid" column="stid"/>
    </collection>
</resultMap>

2.按查询嵌套处理

<!--    按查询嵌套处理-->
<select id="getTeacher2" resultMap="TeacherStudent2">
    select * from teacher where id = #{tid}
</select>
<resultMap id="TeacherStudent2" type="Teacher">
    <result property="id" column="id" />
    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudent" column="id" />
</resultMap>
<select id="getStudent" resultMap="Student1">
    select * from student where uid = #{id}
</select>
<resultMap id="Student1" type="Student">
    <result property="tid" column="uid"/>
</resultMap>

八、动态SQL

1.什么是动态sql

  • 动态 SQL 是 MyBatis 的强大特性之一。彻底摆脱JDBC sql语句拼接痛苦;
  • MyBatis 显著地提升了动态sql的易用性;

2.动态sql特点:

  • 动态sql根据不同条件,生成不同的sql语句
  • 动态sql就是在sql语句中使用逻辑判断语句if、where、set、choose、otherwise、foreach

3.动态sql-if

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

if语句会判断#{title}是否为空,如果为空将舍去 AND title like #{title}

多条件查询:

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

4.动态sql-choose、when、otherwise

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

choose、when、otherwise作用如:开发语言中的switch语句,如果有满足test条件的将不再继续向下执行,如果都不满足将指向****标签的默认语句

5.动态sql-where

注意:

  • 我们不想使用所有的条件,而只是想从多个条件中选择一个使用
  • 前面几个例子已经合宜地解决了一个臭名昭著的动态 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会智能的判断:如果第一个条件不成立,自动将下一条成立的条件之前的 and 或者 or 删除;尽可能保证sql语句的正确;

6.动态sql-set

动态更新语句的类似解决方案叫做 setset 元素可以用于动态包含需要更新的列,忽略其它不更新的列

<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 关键字,并会删掉额外的逗号

7.动态sql-trim

trim 元素可以来自定义set、where标签

1.set元素等价的自定义 trim 元素
<trim prefix="SET" suffixOverrides=",">
  ...
</trim>

suffixOverrides 定义覆盖的后缀值

prefix属性中指定的内容

2.where 元素等价的自定义 trim 元素
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容

8.foreach

  • foreach标签用于对集合进行遍历(尤其是在构建 IN 条件语句的时候)

  • foreach 元素的功能非常强大,它允许你指定一个集合,声明可以在元素体内使用的集合项(item)和索引(index)变量

  • 它也允许你指定开头与结尾的字符串以及集合项迭代之间的分隔符

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

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

9.动态sql-sql片段

在mybatis的mysql中可以提取公共的使用频率较高的sql片段

<!--定义sql片段-->
<sql id="sometable">
    ${prefix}Table
</sql>
<sql id="someinclude">
    from
    <include refid="${include_target}"/>
</sql>

<!--将sql片段引入sql语句,构建sql语句-->
<select id="select" resultType="map">
    select
    field1, field2, field3
    <include refid="someinclude">
        <property name="prefix" value="Some"/>
        <property name="include_target" value="sometable"/>
    </include>
</select>

sql标签定义了sql片段的id

include向sql语句中引入sql片段

​ refid :要引入的sql片段的id

10.动态sql-bind

bind 元素允许你在 OGNL 表达式以外创建一个变量,并可作用于sql语句,以及上下文

<select id="selectBlogsLike" resultType="Blog">
    <bind name="pattern" value="'%' + _parameter.getTitle() + '%'" />
    SELECT * FROM BLOG
    WHERE title LIKE #{pattern}
</select>

九、缓存机制

1.什么是缓存

用户访问访问数据库,对数据库的操作有两种:数据的读写;在用户量较大的情况下,用户将数据库将承受较大的访问压力;在对用户行为分析发现,用户的操作读业务70%~80%,也就是大多数情况下,用户只是查询数据;于是就产生了读写分离的架构,读写分离的架构实现有多种方式;

​ 例:

​ 在业务服务器与数据库之间加入缓存服务器,用户的读操作会首先访问缓存服务器,缓存服务器中没有要查询的数据,再去访问数据库,存在则直接返回数据;

根据上述例子看出:

  • 缓存就是一块存储区域或者专门的设备
  • 数据暂存再缓存中,会大幅度降低数据库的访问压力,系统的访问速度
  • 缓存中适合存放经常查询的、不变的数据,不适合经常修改的数据

2.Mybatis缓存

mybatis提供了一级缓存、二级缓存、自定义缓存接口

  • 一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。

  • 二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为Mapper(Namespace),并且可自定义存储源,如Ehcache。

  • 对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。

3.一级缓存

默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存;

@Test
public void test1(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    userMapper mapper = sqlSession.getMapper(userMapper.class);
    User user1 = mapper.getUserById(1);
    System.out.println("--------------------------");
    User user2 = mapper.getUserById(1);
    System.out.println(user1==user2);
    sqlSession.close();
}

结果返回ture,会发现user1与user2的地址是相等的,也就是说user1、user2在内存中的地址是相等的,即一个对象(对查询而言)

在控制台日志当中发现只执行了一次sql语句查询,也就是说在第一次查询时,mybatis将user1放在了缓存中,在第二次访问时并没有访问数据库,而是访问的缓存

@Test
public void test1(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    userMapper mapper = sqlSession.getMapper(userMapper.class);
    User user1 = mapper.getUserById(1);
    user1.setUserName("zzzz");
    mapper.updateUser(user1);
    System.out.println("--------------------------");
    User user2 = mapper.getUserById(1);
    System.out.println(user1==user2);
    sqlSession.close();
}

结果返回false,会发现两次查询都访问了数据库

结论:

  • mybatis默认开启一级缓存,会将查询会将结果放入缓存
  • 增删改会破坏缓存,mybatis将重新访问数据库,再次查询时刷新缓存数据库
  • 一级缓存在**sqlSession.open()**自动开启
  • 一级缓存在**sqlSession.close()/Clear()**自动关闭
4.二级缓存
1.二级缓存显式开启

在mybatis-conf.xml全局配置文件中配置:(默认是开启的)

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

要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:(二级缓存基于namespace,与****当前所在的mapper才会开启缓存

<cache/>
  • 映射语句文件中的所有 select 语句的结果将会被缓存。

  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。

  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)来清除不需要的缓存。

  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。

  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。

  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

    注意:

    缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

2.二级缓存配置
<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

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

缓存清除策略:(默认的清除策略是 LRU)

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  • flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

  • size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。

  • readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

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

总结:

  • 当一级缓存close()后,数据会转存在二级缓存中
  • 实体类要实现序列化结构,负责会报错
5.自定义缓存-ehcache

除了上述自定义缓存的方式,你也可以通过实现你自己的缓存,或为其他第三方缓存方案创建适配器,来完全覆盖缓存行为

导入ehcache依赖

<!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.1.0</version>
</dependency>

在mapper.xml文件中引入

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

即可在mybatis中使用ehcache,也可在ehcache.xml文件中配置ehcache

ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    <diskStore path="./tmpdir/Tmp_EhCache"/>

    <defaultCache
                  eternal="false"
                  maxElementsInMemory="10000"
                  overflowToDisk="false"
                  diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200"
                  memoryStoreEvictionPolicy="LRU"/>

    <cache
           name="cloud_user"
           eternal="false"
           maxElementsInMemory="5000"
           overflowToDisk="false"
           diskPersistent="false"
           timeToIdleSeconds="1800"
           timeToLiveSeconds="1800"
           memoryStoreEvictionPolicy="LRU"/>
</ehcache>

当前市场使用最热度最高的缓存时redis

3.缓存原理

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值