mybatis

1.2 持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(Jdbc),io文件持久化。

为什么要持久化

  • 有一些对象,不能让他丢掉
  • 内存太贵

1.3 持久层

Dao层、Service层、Controller层

  • 完成持久化工作的代码块
  • 层界限十分明显

1.4 为什么需要MyBatis

  • 帮助程序员将数据存入到数据库中
  • 方便
  • 传统的JDBC代码太复杂了,简化,框架,自动化
  • 不用MyBatis也可以,技术没有高低之分

优点

  • 简单易学
  • 灵活
  • sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射
  • 提供对象关系映射标签,支持对象关系组建维护
  • 提供xml标签,支持编写动态sql

1.5 官方文档地址

https://mybatis.org/mybatis-3/zh

1.6 笔记部分使用的数据库结构以及数据

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user`  (
  `id` int(20) NOT NULL,
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `pwd` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '狂神', '123456');
INSERT INTO `user` VALUES (2, '李四', '452432');
INSERT INTO `user` VALUES (3, '王五', '343122');

1.7 基本使用

MyBatis详细执行流程

在这里插入图片描述

1.7.1 安装

maven导入

<dependencies>
     <!--先导入数据库连接包-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.25</version>
    </dependency>

     <!--导入mybatis的包-->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.6</version>
    </dependency>
    <!-- 使用log4j输出更多的日志信息 -->
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.17</version>
    </dependency>
</dependencies>

<!--还要配置解决maven的资源过滤问题,否则有可能读取不到xml文件-->
<build>
    <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>
</build>

1.7.2 核心配置文件

编写

<?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?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <!--用户名配置-->
                <property name="username" value="root"/>
                <!--密码配置-->
                <property name="password" value="yqlzmzr1314"/>
            </dataSource>
        </environment>
    </environments>
    
    <!--    每一个mapper.xml都需要在MyBatis核心配置文件中注册-->
    <mappers>
        <mapper resource="com/yqlzmzr/dao/UserMapper.xml"/>
        <!-- 这里可以使用通配,这样可以不用每一个都要写
        <mapper resource="com/yqlzmzr/dao/*Mapper.xml"/>"
-->
    </mappers>
</configuration>

加载(这里写了一个工具类)

public class MybatisUtil {
    private static SqlSessionFactory sqlSessionFactory = null;

    static {
        //因为是放在资源文件夹内,所以编译以后配置文件存在于项目根目录
        String resource = "mybatis-config.xml";
        try {
            //加载配置文件的输入流
            InputStream resourceAsStream = Resources.getResourceAsStream(resource);
            
            //创建 sql会话工厂 对象 ,并通过配置文件构建
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


    public static SqlSession getSqlSession() {
        //返回sql会话工厂对象生成的  sqlsession   ,类似于jdbc里面的preparedstatement
        return sqlSessionFactory.openSession();
    }
}

1.7.3 创建实体类

对应数据库字段

public class User {

  private Integer id;
  private String name;
  private String pwd;

  public User(){

  }

  public User(Integer id, String name, String pwd) {
    this.id = id;
    this.name = name;
    this.pwd = pwd;
  }

  public Integer getId() {
    return id;
  }

  public void setId(Integer 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 "User{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", pwd='" + pwd + '\'' +
            '}';
  }
}

1.7.4 编写Mapper层接口

其实就是Dao层

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

img

1.7.5 编写Mapper层接口的实现配置文件

这里给出一个基于 XML 映射sql语句

<?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.yqlzmzr.dao.UserDao">
    <!--    id代表对应的mapper接口的方法名-->
    <!--   resultType 填写实体类,代表返回类型 -->
    <select id="getUserList" resultType="com.yqlzmzr.domain.User">
        select *
        from `user`
    </select>
</mapper>

1.7.6 使用

编写测试类

@Test
public void test() {
    //获取sqlSession对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    //方式一,推荐
    UserDao mapper = sqlSession.getMapper(UserDao.class);
    List<User> userList = mapper.getUserList();

    //方式二,不推荐
    //List<User> userList = sqlSession.selectList("com.yqlzmzr.dao.UserDao.getUserList");

    for (User user : userList) {
        System.out.println(user.toString());
    }

    sqlSession.close();

}

image-20210915004653705

1.8 增删改查

先把上面的UserDao改名为UserMapper,不为啥,就为了规范

然后记得改UserMapper.xml里面的namespace

1.8.1 mapper接口

public interface UserMapper {

    User getUserById(Integer id);

    int addUser(User user);

    int updateUser(User user);

    int deleteUser(int id);
}

1.8.2 增

xml

<insert id="addUser" parameterType="com.yqlzmzr.domain.User">
    insert into `user`(`id`, `name`, `pwd`) values(#{id},#{name},#{pwd})
</insert>

测试类

@Test
public void addUser() {
    //获取sqlSession对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    //方式一,推荐
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int resCount = mapper.addUser(new User(5, "赵六", "zhaoliu"));
    sqlSession.commit();
    System.out.println(resCount);
    sqlSession.close();
}

1.8.3 删

xml

<delete id="deleteUser" parameterType="int">
    delete from `user` where id=#{id}
</delete>

测试类

@Test
public void deleteUser() {
    //获取sqlSession对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int resCount = mapper.deleteUser(1);
    System.out.println(resCount);
    sqlSession.commit();
    sqlSession.close();
}

1.8.4 改

xml

<update id="updateUser" parameterType="com.yqlzmzr.domain.User">
    update `user` set `name`=#{name}, `pwd`=#{pwd} where `id`=#{id}
</update>

测试类

@Test
public void updateUser() {
    //获取sqlSession对象
    SqlSession sqlSession = MybatisUtil.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);

    int resCount = mapper.updateUser(new User(1, "新的", "thisispwd"));
    sqlSession.commit();
    System.out.println(resCount);
    sqlSession.close();

}

1.8.5 查

xml

<select id="getUserById" parameterType="int" resultType="com.yqlzmzr.domain.User">
    select *
    from `user`
    where id = #{id}
</select>

测试类

@Testpublic void getUserById() {    //获取sqlSession对象    SqlSession sqlSession = MybatisUtil.getSqlSession();    UserMapper mapper = sqlSession.getMapper(UserMapper.class);    User userById = mapper.getUserById(1);    System.out.println(userById.toString());    sqlSession.close();}

1.9 万能的Map

假设,我们的实体类,或者数据库中的表,字段或者参数过多,我们应当考虑使用Map!

<insert id="addUserByMap" parameterType="map">    insert into `user`(`id`, `name`, `pwd`) values(#{userId},#{userName},#{password})</insert>
 @Testpublic void addUserByMap() {    //获取sqlSession对象    SqlSession sqlSession = MybatisUtil.getSqlSession();    UserMapper mapper = sqlSession.getMapper(UserMapper.class);    HashMap<String, String> params = new HashMap<>();    params.put("userId","6");    params.put("userName","牛掰");    params.put("password","asdasdad");    int resCount = mapper.addUserByMap(params);    System.out.println(resCount);    sqlSession.commit();    sqlSession.close();}

parameterType="map"
Map传递参数,直接在sql中取出key即可!

只有一个基本类型参数的情况下,可以直接在sql中取到!

就是说如果参数是基本数据类型 parameterType="xxx" 可以不用写,也能取到

2.0 模糊查询

在参数中拼接

<select id="likeUserFind" parameterType="String" resultType="com.yqlzmzr.domain.User">    select * from user where name like #{name}</select>
 @Testpublic void likeUserFind(){    SqlSession sqlSession = MybatisUtil.getSqlSession();    UserMapper mapper = sqlSession.getMapper(UserMapper.class);    List<User> users = mapper.likeUserFind("%李%");    for (User user : users) {        System.out.println(user.toString());    }    sqlSession.close();}

在sql语句中添加

 <select id="likeUserFind" parameterType="String" resultType="com.yqlzmzr.domain.User">     select * from user where name like '%' #{name} '%'</select>
 @Testpublic void likeUserFind(){    SqlSession sqlSession = MybatisUtil.getSqlSession();    UserMapper mapper = sqlSession.getMapper(UserMapper.class);    List<User> users = mapper.likeUserFind("李");    for (User user : users) {        System.out.println(user.toString());    }    sqlSession.close();}

2.1 核心配置文件

标签在xml中是有顺序要求的,properties需要写在上方

顺序为:properties、settings、ypeAliases、typeHandlers、objectFactory、objectWrapperFactory、reflectorFactory、plugins、environments、databaseIdProvider、mappers

image-20210916001802645

2.0.1环境配置(environments)

MyBatis 可以配置成适应多种环境

不过要记住:尽管可以配置多个环境,但每个SqlSessionFactory实例只能选择一种环境。
Mybatis默认的事务管理器就是JDBC,连接池:POOLED

image-20210916003405227

2.0.2 属性(properties)

以标签的形式定义(配置文件不是完整的,只是为了方便演示)

<properties>    <!-- 定义属性名和值 name="xxx" xxx为属性名  value="yyy" yyy为属性的值-->    <property name="username" value="dev_user"/>    <property name="password" value="F2Fa3!33TYyg"/></properties><environments default="development">    <environment id="development">        <transactionManager type="JDBC"/>        <dataSource type="POOLED">            <!-- 使用属性  ${属性名}-->            <property name="username" value="${username}"/>            <property name="password" value="${password}"/>        </dataSource>    </environment></environments>

以properties文件导入属性

创建文件 db.properties

username=thisisnamepassword=asdfdfa

使用

<!--单标签闭合 resource 资源文件路径--><properties resource="db.properties" />   <environments default="development">    <environment id="development">        <transactionManager type="JDBC"/>        <dataSource type="POOLED">            <!-- 使用方法一样-->            <property name="username" value="${username}"/>            <property name="password" value="${password}"/>        </dataSource>    </environment></environments>

注意

当两者同时使用时,默认外部文件的优先级最高

2.2 类型别名

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

方式一:全限定名起别名

<typeAliases>    <!--将com.yqlzmzr.domain.User全限定名起别名为User-->    <typeAlias type="com.yqlzmzr.domain.User" alias="User"/></typeAliases>

使用:

<!--这里的resultType=“User”使用了com.yqlzmzr.domain.User的别名--><select id="getUserList" resultType="User">    select *    from `user`</select>

方式二:指定包下的全部javabean起别名

<typeAliases>    <package name = "com.kuang.pojo"/></typeAliases>

这样会直接把整个包下面的类起别名,然后默认别名为类名全小写,适合要重命名比较多的情况下

第一种可以diy别名,第二种默认是小写,要diy则需要给类添加上注解

@Alias("temp")public class User{}// 此时的别为就为 temp

剩下的属性可以查看文档,使用较少

mybatis默认情况下帮我们起的别名:

别名映射的类型
_bytebyte
_longlong
_shortshort
_intint
_integerint
_doubledouble
_floatfloat
_booleanboolean
stringString
byteByte
longLong
shortShort
intInteger
integerInteger
doubleDouble
floatFloat
booleanBoolean
dateDate
decimalBigDecimal
bigdecimalBigDecimal
objectObject
mapMap
hashmapHashMap
listList
arraylistArrayList
collectionCollection
iteratorIterator

2.3 结果集映射 (resultMap)

结果集映射

数据库 id name pwd

实体类 id name password

他们两的字段名不一样,可以使用resultMap来映射结果集

<!--结果集映射--><resultMap id="UserMap" type="User">    <!--column数据库中的字段,property实体类中的属性-->    <result column="id" property="id"></result>    <result column="name" property="name"></result>    <result column="pwd" property="password"></result></resultMap>//这里的resultMap对应上面的 resultMap标签的id<select id="getUserList" resultMap="UserMap">    select * from USER</select>
  • resultMap 元素是 MyBatis 中最重要最强大的元素。
  • ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。
  • ResultMap 的优秀之处——你完全可以不用显式地配置它们。

2.4 日志

2.4.1 日志工厂

logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J \LOG4J \LOG4J2 \JDK_LOGGING \COMMONS_LOGGING \STDOUT_LOGGING \NO_LOGGING未设置

掌握 STDOUT_LOGGING LOG4J

标准日志工厂 STDOUT_LOGGING

​ 不需要额外导入库

在核心配置文件中配置

<settings>    <setting name="logImpl" value="STDOUT_LOGGING"/></settings>

image-20210917001936463

2.4.2 Log4j

log4j配置到mybatis

添加项目依赖

<dependency>    <groupId>log4j</groupId>    <artifactId>log4j</artifactId>    <version>1.2.17</version></dependency>

新建配置文件 log4j.properties

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码log4j.rootLogger=DEBUG,console,file#控制台输出的相关设置log4j.appender.console = org.apache.log4j.ConsoleAppenderlog4j.appender.console.Target = System.outlog4j.appender.console.Threshold=DEBUGlog4j.appender.console.layout = org.apache.log4j.PatternLayoutlog4j.appender.console.layout.ConversionPattern=[%c]-%m%n#文件输出的相关设置log4j.appender.file = org.apache.log4j.RollingFileAppenderlog4j.appender.file.File=./log/rzp.loglog4j.appender.file.MaxFileSize=10mblog4j.appender.file.Threshold=DEBUGlog4j.appender.file.layout=org.apache.log4j.PatternLayoutlog4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n#日志输出级别log4j.logger.org.mybatis=DEBUGlog4j.logger.java.sql=DEBUGlog4j.logger.java.sql.Statement=DEBUGlog4j.logger.java.sql.ResultSet=DEBUGlog4j.logger.java.sq1.PreparedStatement=DEBUG

这个配置文件很多,没有必要记住,知道他是干什么的就行了,具体可以百度,上面这个实例是比较常用的配置

Log4j简单使用

在要使用Log4j的类中,导入包

import org.apache.log4j.Logger;

日志对象,参数为当前类的class对象

Logger logger = Logger.getLogger(UserDaoTest.class);

应用(日志级别)

用来设置日志级别,然后输出到你设置的地方,比如我配置了输出到文件,而且我用了error级别,然后就有了这种效果方便上线以后查看错误日志

image-20210917225337053

日志级别有:

  • DEBUG
  • INFO
  • WARN
  • ERROR
  • FATAL

级别顺序:DEBUG < INFO < WARN < ERROR < FATAL

这里Log4j有一个规则:假设设置了级别为P,如果发生了一个级别Q比P高,则可以启动,否则屏蔽掉。

如:等级设为Error,warn,info,debug的信息不会输出

修改日志输出的级别要在log4j文件中进行配置
项目正式发布后,一般会把日志级别设置为fatal或者error

image-20210917225706590

各个级别之间的差别:

查看博客:https://www.cnblogs.com/ruiati/p/5997326.html

2.5分页

2.5.1 使用sql-limit

使用map传递参数

<select id="getUserList" parameterType="map" resultType="User">    select *  from `user` limit #{startIndex},#{pageSize};</select>

从第0条数据开始,查找两条

public void test(){
    SqlSession sqlSession = MybatisUtil.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    Map<String, Integer> pageInfo = new HashMap<>();
    pageInfo.put("startIndex",0);
    pageInfo.put("pageSize",2);
    List<User> userList = mapper.getUserList(pageInfo);
    for(User user:userList){
        System.out.println(user.toString());
    }
    sqlSession.close();
}

结果

image-20210917233637557

2.5.2 RowBounds分页

不再使用SQL实现分页

接口:

//分页2
List<User> getUserByRowBounds();

mapper.xml

<!--分页查询2-->
<select id="getUserByRowBounds">
    select * from user
</select>

测试代码

public void getUserByRowBounds(){    SqlSession sqlSession = MybatisUtils.getSqlSession();    //RowBounds实现    RowBounds rowBounds = new RowBounds(1, 2);    //通过Java代码层面实现分页    List<User> userList = sqlSession.selectList("com.kaung.dao.UserMapper.getUserByRowBounds", null, rowBounds);    for (User user : userList) {        System.out.println(user);    }    sqlSession.close();}

2.5.3 分页插件

可以等需要用了在学习,学习成本不高,一般在框架时使用

在这里插入图片描述

2.6 使用注解开发

  1. 注解在接口上实现
@Select("select * from user")List<User> getUsers();
  1. 需要在核心配置文件中绑定接口
<mappers>    <mapper class="com.yqlzmzr.dao.UserMapper"/></mappers>

注解CURD

//方法存在多个参数,所有的参数前面必须加上@Param("id")注解@Delete("delete from user where id = ${uid}")int deleteUser(@Param("uid") int id);

关于@Param( )注解

  • 基本类型的参数或者String类型,需要加上
  • 引用类型不需要加
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上
  • 我们在SQL中引用的就是我们这里的@Param()中设定的属性名

下面要用的(环境搭建)

student

/*
 Navicat Premium Data Transfer

 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 50734
 Source Host           : localhost:3306
 Source Schema         : mybatis

 Target Server Type    : MySQL
 Target Server Version : 50734
 File Encoding         : 65001

 Date: 28/09/2021 20:25:30
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for student
-- ----------------------------
DROP TABLE IF EXISTS `student`;
CREATE TABLE `student`  (
  `id` int(10) NOT NULL,
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  `tid` int(10) NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE,
  INDEX `fktid`(`tid`) USING BTREE,
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of student
-- ----------------------------
INSERT INTO `student` VALUES (1, '小明', 1);
INSERT INTO `student` VALUES (2, '小红', 1);
INSERT INTO `student` VALUES (3, '小张', 1);
INSERT INTO `student` VALUES (4, '小李', 1);
INSERT INTO `student` VALUES (5, '小王', 1);

SET FOREIGN_KEY_CHECKS = 1;

Teacher

/*
 Navicat Premium Data Transfer

 Source Server         : 本地数据库
 Source Server Type    : MySQL
 Source Server Version : 50734
 Source Host           : localhost:3306
 Source Schema         : mybatis

 Target Server Type    : MySQL
 Target Server Version : 50734
 File Encoding         : 65001

 Date: 28/09/2021 20:25:40
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for teacher
-- ----------------------------
DROP TABLE IF EXISTS `teacher`;
CREATE TABLE `teacher`  (
  `id` int(10) NOT NULL,
  `name` varchar(30) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of teacher
-- ----------------------------
INSERT INTO `teacher` VALUES (1, '秦老师');

SET FOREIGN_KEY_CHECKS = 1;

实体类

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}

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

    //一个老师拥有多个学生
    private List<Student> students;
}+

2.7、多对一处理

多个学生一个老师;

1. 测试环境搭建

  1. 导入lombok
  2. 新建实体类Teacher,Student
  3. 建立Mapper接口
  4. 建立Mapper.xml文件
  5. 在核心配置文件中绑定注册我们的Mapper接口或者文件 【方式很多,随心选】
  6. 测试查询是否能够成功

2. 按照查询嵌套处理

<!--
     思路:
        1. 查询所有的学生信息
        2. 根据查询出来的学生的tid寻找特定的老师 (子查询)
    -->
<select id="getStudent" resultMap="StudentTeacher">
    select * from student
</select>
<resultMap id="StudentTeacher" type="student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--复杂的属性,我们需要单独出来 对象:association 集合:collection-->
    <collection property="teacher" column="tid" javaType="teacher" select="getTeacher"/>
</resultMap>
<select id="getTeacher" resultType="teacher">
    select * from teacher where id = #{id}
</select>

3.按照结果嵌套处理

<!--按照结果进行查询-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid , s.name sname, t.name tname
    from student s,teacher t
    where s.tid=t.id
</select>
<!--结果封装,将查询出来的列封装到对象属性中-->
<resultMap id="StudentTeacher2" type="student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="teacher">
        <result property="name" column="tname"></result>
    </association>
</resultMap>

2.8、一对多处理

一个老师多个学生;

对于老师而言,就是一对多的关系;

按照结果嵌套嵌套处理

<!--按结果嵌套查询-->
<select id="getTeacher" resultMap="StudentTeacher">
    SELECT s.id sid, s.name sname,t.name tname,t.id tid FROM student s, teacher t
    WHERE s.tid = t.id AND tid = #{tid}
</select>
<resultMap id="StudentTeacher" type="Teacher">
    <result property="id" column="tid"/>
    <result property="name" column="tname"/>
    <!--复杂的属性,我们需要单独处理 对象:association 集合:collection
    javaType=""指定属性的类型!
    集合中的泛型信息,我们使用ofType获取
    -->
    <collection property="students" ofType="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

小结

  1. 关联 - association 【多对一】
  2. 集合 - collection 【一对多】
  3. javaType & ofType

    1. JavaType用来指定实体类中的类型
    2. ofType用来指定映射到List或者集合中的pojo类型,泛型中的约束类型

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂
  • 注意一对多和多对一,属性名和字段的问题
  • 如果问题不好排查错误,可以使用日志,建议使用Log4j

面试高频

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

下面要用的(环境搭建)

blog sql

CREATE TABLE `blog`  (
  `id` varchar(50) NOT NULL COMMENT '博客id',
  `title` varchar(100) NOT NULL COMMENT '博客标题',
  `author` varchar(30) NOT NULL COMMENT '博客作者',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE = InnoDB CHARACTER SET = utf8;

实体类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Blog {

    private String id;
    private String title;
    private String author;
    private Date createTime;
    private Integer views;

}

2.9、动态SQL

什么是动态SQL:动态SQL就是根据不同的条件生成不同的SQL语句

所谓的动态SQL,本质上还是SQL语句,只是我们可以在SQL层面,去执行一个逻辑代码

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

编写实体类对应Mapper接口和Mapper.xml文件

IF

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <if test="title!=null">
            and title = #{title}
        </if>
        <if test="author!=null">
            and author = #{author}
        </if>
    </where>
</select>

choose (when, otherwise)

看文档

trim、where、set

看文档

SQL片段

有的时候,我们可能会将一些功能的部分抽取出来,方便复用!

这个文档貌似没有

  1. 使用SQL标签抽取公共部分可
<sql id="if-title-author">
    <if test="title!=null">
        title = #{title}
    </if>
    <if test="author!=null">
        and author = #{author}
    </if>
</sql>
  1. 在需要使用的地方使用Include标签引用即可
<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from blog
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>

注意事项:

  • SQL标签里面不要存在where标签

动态SQL就是在拼接SQL语句,我们只要保证SQL的正确性,按照SQL的格式,去排列组合就可以了

3.0、缓存

3.0.1 简介

查询 : 连接数据库,耗资源

一次查询的结果,给他暂存一个可以直接取到的地方 --> 内存:缓存

我们再次查询的相同数据的时候,直接走缓存,不走数据库了

  1. 什么是缓存[Cache]?

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

    1. 减少和数据库的交互次数,减少系统开销,提高系统效率
  3. 什么样的数据可以使用缓存?

    1. 经常查询并且不经常改变的数据 【可以使用缓存】

3.0.2 MyBatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便的定制和配置缓存,缓存可以极大的提高查询效率
  • MyBatis系统中默认定义了两级缓存:一级缓存和二级缓存

    • 默认情况下,只有一级缓存开启(SqlSession级别的缓存,也称为本地缓存)
    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
    • 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。

3.0.3 一级缓存

默认开启一级缓存

  • 一级缓存也叫本地缓存:SqlSession

    • 与数据库同一次会话期间查询到的数据会放在本地缓存中
    • 以后如果需要获取相同的数据,直接从缓存中拿,没必要再去查询数据库

测试步骤:

  1. 开启日志
  2. 测试在一个Session中查询两次记录
 @Test
public void test1() {
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    User user = mapper.getUserById(1);
    System.out.println(user);

    System.out.println("=====================================");

    User user2 =  mapper.getUserById(1);
    System.out.println(user2 == user);
}
  1. 查看日志输出

在这里插入图片描述

缓存失效的情况:

  1. 查询不同的东西
  2. 增删改操作,可能会改变原来的数据,所以必定会刷新缓存
  3. 查询不同的Mapper.xml
  4. 手动清理缓存

    sqlSession.clearCache();

3.0.4 二级缓存

  • 二级缓存也叫全局缓存,一级缓存作用域太低了,所以诞生了二级缓存
  • 基于namespace级别的缓存,一个名称空间,对应一个二级缓存
  • 工作机制

    • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中
    • 如果会话关闭了,这个会员对应的一级缓存就没了;但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中
    • 新的会话查询信息,就可以从二级缓存中获取内容
    • 不同的mapper查询出的数据会放在自己对应的缓存(map)中

一级缓存开启(SqlSession级别的缓存,也称为本地缓存)

  • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。
  • 为了提高可扩展性,MyBatis定义了缓存接口Cache。我们可以通过实现Cache接口来定义二级缓存。

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

  1. 开启缓存

在mybatis的配置文件中

<!--开启全局缓存(默认是开启的,显式的开启易于代码阅读)-->
<setting name="cacheEnabled" value="true"/>
  1. 在xxxMapper.xml中设置缓存

    <!--直接添加(使用默认值)-->
    <cache/>

基本上就是这样。这个简单语句的效果如下:

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

或者显示的配置:

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

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

可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。

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

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

<font color='red'>注意:开启二级缓存需要将实体类序列化</font>

测试:

SqlSession sqlSession = MybatisUtil.getSqlSession();
IBlogMapper mapper = sqlSession.getMapper(IBlogMapper.class);
Blog all = mapper.getById("1");
sqlSession.close();

SqlSession sqlSession2 = MybatisUtil.getSqlSession();
IBlogMapper mapper2 = sqlSession2.getMapper(IBlogMapper.class);
Blog all2 = mapper2.getById("1");
sqlSession2.close();
System.out.println(all==all2);

查询只查询了一遍,当第一个开始查询是结果放在以及缓存,直至第一个sql会话被关闭,结果被转移到二级缓存,第二个查询是去二级缓存查找是否存在

image-20211005143024732

小结:

  • 只要开启了二级缓存,在同一个Mapper下就有效
  • 所有的数据都会放在一级缓存中
  • 只有当前会话提交,或者关闭的时候,才会提交到二级缓存中
最后修改:2021 年 10 月 05 日 03 : 05 PM