MyBatis--狂神

感谢狂神的分享:B站视频地址

推荐文章:https://mp.weixin.qq.com/s/GgHU18NS_n2F1EE-gYPu7Q

官网地址:https://mybatis.org/mybatis-3/
github地址:https://github.com/search?q=mybatis

1、简介

mybatis

什么是MyBatis

  • MyBatis本是apache的一个开源项目iBatis,2010年这个项目由apache software foundation迁移到了google code,并且改名为MyBatis。2013年11月迁移到Github。
  • iBatis 一词来源于internetabatis的组合,是一个基于Java的持久层框架。iBatis提供的持久层框架包括SQL Maps和Data Access Objects
  • MyBatis 支持自定义 SQL、存储过程以及高级映射。
  • MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。
  • MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

maven依赖

可以去maven库中,下载最新版:https://mvnrepository.com/

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

MyBatis特点

  • 简单易学
  • 灵活: sql写在xml里,便于统一管理和优化。
  • 解除sql与程序代码的耦合:sql和代码的分离,提高了可维护性。
  • 提供映射标签,支持对象与数据库的orm字段关系映射。
  • 提供对象关系映射标签,支持对象关系组建维护。
  • 提供xml标签,支持编写动态sql。

2、第一个MyBatis程序

1、创建数据库,建造实体类

CREATE DATABASE `mybatis`;

USE `mybatis`;

CREATE TABLE `user`(
  `id` INT(20) NOT NULL PRIMARY KEY,
  `name` VARCHAR(30) DEFAULT NULL,
  `pwd` VARCHAR(30) DEFAULT NULL
)ENGINE=INNODB DEFAULT CHARSET=utf8;

INSERT INTO `user`(`id`,`name`,`pwd`) VALUES 
(1,'狂神','123456'),
(2,'张三','123456'),
(3,'李四','123890')
public class User {
    private int id;
    private String name;
    private String pwd;
	//省略get/set方法、构造方法。。。

2、创建一个普通的maven项目,删除 src 文件夹,添加如下依赖:

	<!--mysql 驱动-->
<dependencys>
	<dependency>
	    <groupId>mysql</groupId>
	    <artifactId>mysql-connector-java</artifactId>
	    <version>8.0.29</version>
	</dependency>
	<!-- mybatis -->
	<dependency>
	    <groupId>org.mybatis</groupId>
	    <artifactId>mybatis</artifactId>
	    <version>3.5.10</version>
	</dependency>
	<!-- junit测试 -->
	<dependency>
	    <groupId>junit</groupId>
	    <artifactId>junit</artifactId>
	    <version>4.12</version>
	</dependency>
</dependencys>

<build>
    <!--为防止mapper.xml文件无法输出,需进行如下配置-->
    <resources>
        <!-- src/main/java文件夹下,可以存放 *.properties、*.xml 文件,不用过滤 -->
        <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>

3、在父项目下,创建一个maven项目的 module,并在Resource下创建mybatis核心配置文件

<?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>
	<!-- 可以配置多个数据源,默认指定development -->
    <environments default="development">
    	<!-- 数据源:  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/stu?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

	<!-- mapper.xml都要在这里进行注册 -->
    <mappers>
        <mapper resource="com/kuang/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

4、创建mybatis工具类,返回SqlSessionFactory对象

public class MybatisUtils {
    /*
    每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
        而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。
    */
    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();
        }
    }

    /*
    * 有了 SqlSessionFactory,就可以从中获得 SqlSession 的实例。
    * SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。
    */
    public static SqlSession getSqlSession(){
        return sqlSessionFactory.openSession();
    }
}

5、编写持久层接口以及对应xml文件

public interface UserMapper {
    //查询所有用户
    List<User> selectUsers();
    
    //根据名称模糊查询
    List<User> selectUsersByName(String name);
    
    //根据id查询用户
    User getUserById(int id);

    //新增一个用户
    int addUser(User user);

    //根据id修改用户
    int updateUser(User user);

    //根据id删除用户
    int deleteUserById(int id);
}
<?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接口-->
<mapper namespace="com.kuang.mapper.UserMapper">

    <!--具体的方法,id 与接口中的方法名对应-->
   <select id="selectUsers" resultType="com.kuang.pojo.User">
      select * from user ;
   </select>

	<!--根据名称模糊查询-->
    <select id="selectUsersByName" parameterType="string" resultType="com.kuang.pojo.User">
      select * from user where name like concat('%',#{name},'%');
   </select>

	<!--根据id查询用户-->
    <select id="getUserById" parameterType="int" resultType="com.kuang.pojo.User">
        select * from user where id = #{id}
    </select>

    <!--新增一个用户-->
    <insert id="addUser" parameterType="com.kuang.pojo.User">
        insert user(id,name,pwd) values (#{id},#{name},#{pwd})
    </insert>

    <!--修改一个用户-->
    <update id="updateUser" parameterType="com.kuang.pojo.User">
        update user set name = #{name},pwd = #{pwd} where id = #{id}
    </update>

    <!--删除一个用户-->
    <delete id="deleteUserById" parameterType="int">
        delete from user where id = #{id}
    </delete>

</mapper>

6、编写测试类

@Test
public void getAllUser(){
    //获取sqlSession
    SqlSession session = MybatisUtils.getSqlSession();
    //获取mapper
    UserMapper userMapper = session.getMapper(UserMapper.class);
    //调用mapper定义的方法
    List<User> userList = userMapper.selectUsers();
    for (User user : userList) {
        System.out.println(user.toString());
    }
}

//新增一个用户,需要显式提交事务
@Test
public void addUser(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    int i = mapper.addUser(new User(4, "haha", "root123"));
    sqlSession.commit();	//增、删、改需要显式提交事务
    System.out.println(i);
}

项目结构如下:
项目结构

3、配置解析

官网配置文档解析:https://mybatis.org/mybatis-3/zh/configuration.html

在这里插入图片描述

属性(properties)

属性(如数据库连接属性)可以在外部进行配置,并可以进行动态替换:
在这里插入图片描述

环境配置(environments)

MyBatis 可以配置成适应多种环境,但每个 SqlSessionFactory 实例只能选择一种环境。 如果需要连接多个数据库,则每个数据库对应一个 SqlSessionFactory 实例
在这里插入图片描述
环境可以随意命名,但务必保证默认的环境 ID 要匹配其中一个环境 ID

<environments default="development"> <!-- 默认使用的环境 ID(比如:default="development") -->
  
  <!-- 每个 environment 元素定义的环境 ID(比如:id="development") -->
  <environment id="development">
  	<!-- 事务管理器的配置(比如:type="JDBC") 
  	 MyBatis 中有两种类型的事务管理器(type="[JDBC | MANAGED]")
  	-->
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>

	<!-- 数据源的配置(比如:type="POOLED") 
	有三种内建的数据源类型(type="[UNPOOLED|POOLED|JNDI]")
	-->
    <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>

类型别名(typeAliases)

  • 类型别名可为 Java 类型设置一个缩写名字。
  • 意在降低冗余的全限定类名书写。

如下所示,可以在mapper.xml中,用user代替com.kuang.pojo.User

<typeAliases>
    <typeAlias alias="user" type="com.kuang.pojo.User"></typeAlias>
</typeAliases>

也可以指定一个包名,MyBatis 会在包名下面搜索 Java Bean,使用 Bean 的首字母小写的非限定类名来作为它的别名。
如下所示,用 user 作为 com.kuang.pojo.User 的别名:

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

设置(settings)

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下面是mybatis的所有设置及默认值:

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
  <setting name="multipleResultSetsEnabled" value="true"/>
  <setting name="useColumnLabel" value="true"/>
  <setting name="useGeneratedKeys" value="false"/>
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <setting name="defaultStatementTimeout" value="25"/>
  <setting name="defaultFetchSize" value="100"/>
  <setting name="safeRowBoundsEnabled" value="false"/>
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <setting name="localCacheScope" value="SESSION"/>
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>

映射器(mappers)

MapperRegistry:注册绑定我们的Mapper文件。每一个Mapper.xml都需要在Mybatis核心配置文件中注册!
方式一:【推荐使用】

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

方式二:使用class文件绑定注册

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

注意点:

  • 接口和它的Mapper配置文件必须同名!
  • 接口和它的Mapper配置文件必须在同一个包下!

方式三:使用扫描包进行注入绑定

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

注意点:

  • 接口和它的Mapper配置文件必须同名!
  • 接口和它的Mapper配置文件必须在同一个包下!

作用域(Scope)和生命周期

作用域和生命周期类别是至关重要的,错误的使用会导致非常严重的 并发问题
在这里插入图片描述

SqlSessionFactoryBuilder:一旦创建,就不再需要它了。最佳作用域是方法作用域(也就是局部方法变量)

SqlSessionFactory:一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例
最佳作用域是应用作用域,最简单的就是使用单例模式或者静态单例模式

SqlSession
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
每次收到 HTTP 请求,就可以打开一个 SqlSession,返回一个响应后,就关闭它:
在这里插入图片描述
这里的每一个Mapper,就代表一个具体的业务!

4、ResultMap

解决 数据库字段名、实体类属性名 不一致问题,有以下两种方法:

采用mapUnderscoreToCamelCase属性设置(字段与属性有驼峰映射关系)
数据库字段:a_column ,实体类属性:aColumn

<settings>
    <!--是否开启驼峰命名自动映射,即从数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

ResultMap (字段与属性,没有任何关联)

数据库与实体类如下:

数据库:	id  name  pwd
java类:id  name  password
<!--结果集映射-->
<resultMap id="UserMap" type="User">
    <!--column数据库中的字段,property实体类中的属性-->
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <result column="pwd" property="password"/>
</resultMap>

<select id="getUserById" resultMap="UserMap">
    select * from mybatis.user where id = #{id}
</select>

5、日志

日志工厂

Mybatis 通过使用内置的日志工厂提供日志功能,把日志工作委托给下面的实现之一:
SLF4J
Apache Commons Logging
Log4j 2
Log4j (3.5.9 起废弃)
JDK logging

通过mybatis-config.xml中的 setting 设置日志的具体实现。logImpl 可选的值有:SLF4J、LOG4J(3.5.9 起废弃)、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了接口 org.apache.ibatis.logging.Log 的,且构造方法是以字符串为参数的类的完全限定名

STDOUT_LOGGING标准日志输出,配置如下:

<settings>
    <!-- 配置日志的具体实现 -->
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

log4j2

Apache Log4j 2 是对 Log4j 的升级:官网地址

具体使用如下:引入jar包,并配置核心配置文件 log4j2.xml

<dependency>
  <groupId>org.apache.logging.log4j</groupId>
  <artifactId>log4j-core</artifactId>
  <version>2.x.x</version>
</dependency>

参考博客:log4j2.xml配置详解

最简配置:

<?xml version="1.0" encoding="UTF-8"?>
 <Configuration status="WARN">
   <Appenders>
     <Console name="Console" target="SYSTEM_OUT">
       <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
     </Console>
   </Appenders>
   <Loggers>
     <Root level="error">
       <AppenderRef ref="Console"/>
     </Root>
   </Loggers>
 </Configuration>

配置模板:

<?xml version="1.0" encoding="UTF-8"?>
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<configuration monitorInterval="5">
  <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->

  <!--变量配置-->
  <Properties>
    <!-- 格式化输出:%date表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度 %msg:日志消息,%n是换行符-->
    <!-- %logger{36} 表示 Logger 名字最长36个字符 -->
    <property name="LOG_PATTERN" value="%date{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n" />
    <!-- 定义日志存储的路径 -->
    <property name="FILE_PATH" value="更换为你的日志路径" />
    <property name="FILE_NAME" value="更换为你的项目名" />
  </Properties>

  <appenders>

    <console name="Console" target="SYSTEM_OUT">
      <!--输出日志的格式-->
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <!--控制台只输出level及其以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
    </console>

    <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,适合临时测试用-->
    <File name="Filelog" fileName="${FILE_PATH}/test.log" append="false">
      <PatternLayout pattern="${LOG_PATTERN}"/>
    </File>

    <!-- 这个会打印出所有的info及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileInfo" fileName="${FILE_PATH}/info.log" filePattern="${FILE_PATH}/${FILE_NAME}-INFO-%d{yyyy-MM-dd}_%i.log.gz">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <Policies>
        <!--interval属性用来指定多久滚动一次,默认是1 hour-->
        <TimeBasedTriggeringPolicy interval="1"/>
        <SizeBasedTriggeringPolicy size="10MB"/>
      </Policies>
      <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
      <DefaultRolloverStrategy max="15"/>
    </RollingFile>

    <!-- 这个会打印出所有的warn及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileWarn" fileName="${FILE_PATH}/warn.log" filePattern="${FILE_PATH}/${FILE_NAME}-WARN-%d{yyyy-MM-dd}_%i.log.gz">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/>
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <Policies>
        <!--interval属性用来指定多久滚动一次,默认是1 hour-->
        <TimeBasedTriggeringPolicy interval="1"/>
        <SizeBasedTriggeringPolicy size="10MB"/>
      </Policies>
      <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
      <DefaultRolloverStrategy max="15"/>
    </RollingFile>

    <!-- 这个会打印出所有的error及以下级别的信息,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
    <RollingFile name="RollingFileError" fileName="${FILE_PATH}/error.log" filePattern="${FILE_PATH}/${FILE_NAME}-ERROR-%d{yyyy-MM-dd}_%i.log.gz">
      <!--控制台只输出level及以上级别的信息(onMatch),其他的直接拒绝(onMismatch)-->
      <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/>
      <PatternLayout pattern="${LOG_PATTERN}"/>
      <Policies>
        <!--interval属性用来指定多久滚动一次,默认是1 hour-->
        <TimeBasedTriggeringPolicy interval="1"/>
        <SizeBasedTriggeringPolicy size="10MB"/>
      </Policies>
      <!-- DefaultRolloverStrategy属性如不设置,则默认为最多同一文件夹下7个文件开始覆盖-->
      <DefaultRolloverStrategy max="15"/>
    </RollingFile>

  </appenders>

  <!--Logger节点用来单独指定日志的形式,比如要为指定包下的class指定不同的日志级别等。-->
  <!--然后定义loggers,只有定义了logger并引入的appender,appender才会生效-->
  <loggers>

    <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
    <logger name="org.mybatis" level="info" additivity="false">
      <AppenderRef ref="Console"/>
    </logger>
    <!--监控系统信息-->
    <!--若是additivity设为false,则 子Logger 只会在自己的appender里输出,而不会在 父Logger 的appender里输出。-->
    <Logger name="org.springframework" level="info" additivity="false">
      <AppenderRef ref="Console"/>
    </Logger>

    <root level="info">
      <appender-ref ref="Console"/>
      <appender-ref ref="Filelog"/>
      <appender-ref ref="RollingFileInfo"/>
      <appender-ref ref="RollingFileWarn"/>
      <appender-ref ref="RollingFileError"/>
    </root>
  </loggers>

</configuration>

简单使用

1、在要使用Log4j2的类中,导入下面两个包:

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger

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

private static Logger logger = LogManager.getLogger(UserTest.class);

3、日志级别

logger.info("info:进入了testLog4j");
logger.debug("DEBUG:进入了testLog4j");
logger.error("erro:进入了testLog4j");

6、分页

使用Limit分页

语法:SELECT * from user limit startIndex [,pageSize]
SELECT  * from user limit 3 #[0,n]

mapper.xml中的下发:

<!--  分页  -->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from mybatis.user limit #{startIndex},#{pageSize}
</select>

RowBounds分页

  1. 接口
List<User> selectByRowBounds();
  1. mapper.xml
<select id="selectByRowBounds" resultMap="UserMap">
    select * from mybatis.user
</select>
  1. 测试代码
@Test
public void getUserByRowBounds(){
    SqlSession sqlSession = MybatisUtils.getSqlSession();
    //RowBounds实现
    RowBounds rowBounds = new RowBounds(0, 2);
    //通过java代码层面实现分页
    List<User> userList = sqlSession.selectList("com.kuang.mapper.UserMapper.selectByRowBounds",null,rowBounds);

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

分页插件 PagerHleper

PageHelper 文档地址:https://pagehelper.github.io/docs/
在这里插入图片描述

7、注解开发

在真正的开发中,很多时候我们会选择面向接口编程。根本原因:** 解耦,可拓展,提高复用,分层开发中,上层不用管具体的实现,大家都遵守共同的标准,使得开发变得更容易,规范性更好**

  • 面向对象是指,我们考虑问题时,以对象为单位,考虑它的属性和方法;
  • 面向过程是指,我们考虑问题时,以一个具体的流程(事务过程)为单位,考虑它的实现;
  • 接口设计与非接口设计是针对复用技术而言的,与面向对象(过程)不是一个问题,更多的体现就是对系统整体的架构;

使用注解开发

在这里插入图片描述
1、在接口的方法上,使用注解编写sql

@Select("select * from user")
List<User> getUsers();

//CRUD
/*
	基本类型的参数或者String类型,需要加上
	引用类型不需要加
	如果只有一个基本类型的话,可以忽略,但是建议都加上!
	在SQL中引用的,就是@Param("")中设定的属性名!
*/
@Select("select * from user where id=#{id}")
User getUserById(@Param("id") int id);

@Insert("insert into user (id,name,pwd) values(#{id},#{name},#{password})")
int addUser(User user);

@Update("update user set name=#{name},pwd=#{password} where id=#{id}")
int updateUser(User user);

@Delete("delete from user where id = #{uid}")
int deleteUser(@Param("uid") int id);

2、在mybatis-config.xml中,注册接口

<mappers>
    <mapper class="com.kuang.dao.UserMapper"/>
</mappers>

注:本质是反射机制,低层实现了动态代理

8、mybatis执行流程

参考博客:SqlSessionTemplate、sqlSessionFactory、sqlSessionFactoryBean、SqlSessionManager
执行流程

9、Lombok

1、在IDEA中安装Lombok插件!
2、在项目pom.xml文件中导入Lombok的jar包

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>xx.xx.xx</version>
</dependency>

3、在实体类上加注解

@Getter and @Setter		//添加到属性
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor	//构造方法
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data 	//添加到实体类:无参构造、get、set、toString、hashCode、equals
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows

10、多对一

1、创建数据库

-- 创建teacher表,并插入数据
CREATE TABLE `teacher` (
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	PRIMARY KEY (`id`)
)ENGINE = INNODB DEFAULT CHARSET=utf8

INSERT INTO teacher(`id`,`name`) VALUES (1,'秦老师');
-- 创建student表,建立外检关联,并插入数据
CREATE TABLE `student` (
	`id` INT(10) NOT NULL,
	`name` VARCHAR(30) DEFAULT NULL,
	`tid` INT(10) DEFAULT NULL,
	PRIMARY KEY (`id`),
	KEY `fktid`(`tid`),
	CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
)ENGINE = INNODB DEFAULT CHARSET=utf8

INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('1','小明','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('2','小红','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('3','小张','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('4','小李','1');
INSERT INTO `student`(`id`,`name`,`tid`) VALUES ('5','小王','1');

2、实体类

//学生类:
public class Student {
    private int id;
    private String name;
    private Teacher teacher;	//使用老师类,作为属性
}
//老师类
public class Student {
    private int id;
    private String name;
}

按照查询嵌套处理

如下所示,getAllStu 查询中,嵌套了getAllTeacher查询。
类似子查询,先查出来student的结果集,再查询teacher

<!--
  思路:
      1.查询所有的学生信息
      2.根据查询出来的学生的tid,寻找对应的老师! 子查询
-->
<select id="getAllStu" resultMap="StudentTeacher">
    select * from student
</select>

<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--  
    		复杂的属性,需要单独处理
          	对象:association     集合:collection
     -->
    <association property="teacher" column="tid" javaType="Teacher" select="getAllTeacher"/>
</resultMap>

<select id="getAllTeacher" resultType="Teacher">
    select * from teacher where id = #{id}
</select>

按照结果嵌套处理

连表查询,查出结果之后,映射在 resultMap中

<select id="getAllStu2" resultMap="StudentTeacher2">
    select s.id sId,s.name sName,t.name tName
        from student s
        LEFT JOIN teacher t on 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"/>
    </association>
</resultMap>

11、一对多

根据查询结果映射(连表查询)

<!-- 先查询出来结果,再映射到集合中 -->
<select id="getTeacherById" resultMap="TeacharStudent">
    select t.id tId,t.name tName,s.id sId,s.name sName
        from teacher t 
        LEFT JOIN student s on t.id = s.tid
        where t.id = #{tId}
</select>

<resultMap id="TeacharStudent" type="Teacher">
    <result column="tId" property="id"/>
    <result column="tName" property="name"/>

    <!--集合中的泛型信息,通过ofType获取-->
    <collection property="studentList" ofType="Student">
        <result column="sId" property="id"/>
        <result column="sName" property="name"/>
    </collection>
</resultMap>

根据查询映射(子查询)

<!-- 通过集合,进行关联查询 -->
<select id="getTeacherById2" resultMap="TeacharStudent2">
    select * from teacher where id = #{tId}
</select>

<resultMap id="TeacharStudent2" type="Teacher">
    <result column="id" property="id"/>
    <result column="name" property="name"/>
    <collection  property="studentList" javaType="ArrayList" ofType="Student" select="selectStuByTid" column="id"/>
</resultMap>

<select id="selectStuByTid" resultType="Student">
    select * from student where tid = #{id}
</select>

12、动态sql

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

if

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

choose、when、otherwise

有时候,只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

传入了 “title” 就按 “title” 查找,传入了 “author” 就按 “author” 查找。
若两者都没有传入,就返回标记为 featured = 1 的 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

where 元素只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

<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 元素与你期望的不太一样,你也可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

属性描述
prefix给sql语句拼接的前缀
suffix给sql语句拼接的后缀
prefixOverrides去除sql语句前面的关键字或者字符,由管道符|分隔。假设该属性指定为"AND",当sql语句的开头为"AND",将被去除。
suffixOverrides去除sql语句后面的关键字或者字符,由管道符 |分隔。
-- 指定sql片段以 where 开始,如果where 后面紧跟 (and、or),则被删除
<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

set 元素可以用于动态包含需要更新的列,忽略其它不更新的列

-- set元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号
<update id="updateAuthorIfNecessary">
  update Author
    <set>
      <if test="username != null">username=#{username},</if>
      <if test="password != null">password=#{password},</if>
      <if test="bio != null">bio=#{bio}</if>
    </set>
  where id=#{id}
</update>

foreach

动态 SQL 的另一个常见使用场景是对集合进行遍历

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

sql 片段

使用SQL标签抽取公共的部分

<sql id="if-title-author">
    <if test="title != null">
        title = #{title}
    </if>
    <if test="author != null">
        and author = #{author}
    </if>
</sql>

在需要使用的地方使用Include标签引用即可

<select id="queryBlogIF" parameterType="map" resultType="Blog">
    select * from mybatis.blog
    <where>
        <include refid="if-title-author"></include>
    </where>
</select>

13、缓存

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

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

  • 二级缓存,需手动开启和配置(基于namespace级别的缓存)

  • 为了提高扩展性,Mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来自定义二级缓存。

一级缓存

一级缓存也叫本地缓存,默认是开启的,只在一次SqlSession中有效,也就是拿到连接到关闭连接这个阶段有效:

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

以下几种情况,会导致缓存失效:

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

二级缓存

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

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

步骤:

  1. 开启全局缓存
<!--开启全局缓存-->
<setting name="cacheEnable" value="true"/>
  1. 在要使用二级缓存的Mapper中开启
下面更高级的配置创建了一个 FIFO 缓存,
每隔 60 秒刷新,
最多可以存储结果对象或列表的 512 个引用,
而且返回的对象被认为是只读的
		因此对它们进行修改可能会在不同线程中的调用者产生冲突。

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>
  1. 每条语句可以自定义与缓存交互的方式,或将它们完全排除于缓存之外,这可以通过在每条语句上使用两个简单属性来达成。
 默认情况下,语句会这样来配置:
 
<select ... flushCache="false" useCache="true"/>
<insert ... flushCache="true"/>
<update ... flushCache="true"/>
<delete ... flushCache="true"/>

总结:

  • pojo类要实现序列化接口
  • 只要开启了二级缓存,只在同一个Mapper下有效
  • 所有的数据都会先放在一级缓存中;只有当会话提交或者关闭的时候,才会提交到二级缓存中

缓存原理

在这里插入图片描述

自定义缓存

Ehcache是一种广泛使用的开源Java分布式缓存,主要面向通用缓存

  1. 导入包
<!-- 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>
  1. ehcache的配置文件
<?xml version="1.0" encoding="UTF8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
        updateCheck="false">
<!--
    diskStore :为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置,参数解释如下:
        user.hoeme - 用户主目录
        user.dir -  用户当前工作目录
        javaio.tmpdir - 默认临时文件
-->
    <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>
    <!--
        defaultCache:默认缓存策略,当cache找不到定义的缓存时,则使用这个缓存策略,只能定义一个
    -->
    <!--
       name:缓存名称。
       maxElementsInMemory:缓存最大个数。
       eternal:对象是否永久有效,一但设置了,timeout将不起作用。
       timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
       timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
       overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
       diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
       maxElementsOnDisk:硬盘最大缓存个数。
       diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
       diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
       memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
       clearOnFlush:内存数量最大时是否清除。
    -->
  1. 使用ehcache
<!--自定义缓存,用第三方缓存覆盖-->
<cache type = "org.mybatis.caches.ehcache.EhcacheCache"/>
  1. 但想要在多个命名空间中共享相同的缓存配置和实例,可以使用 cache-ref 元素来引用另一个缓存。
<cache-ref namespace="com.someone.application.data.SomeMapper"/>


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

<cache type="com.domain.something.MyCustomCache"/>

type 属性指定的类必须实现 org.apache.ibatis.cache.Cache 接口,且提供一个接受 String 参数作为 id 的构造器。 这个接口是 MyBatis 框架中许多复杂的接口之一,但是行为却非常简单。

public interface Cache {
  String getId();
  int getSize();
  void putObject(Object key, Object value);
  Object getObject(Object key);
  boolean hasKey(Object key);
  Object removeObject(Object key);
  void clear();
}
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值