MyBatis学习笔记

1、简介

image-20210129225418682

环境说明:

  • jdk 8 +
  • MySQL 5.7.19
  • maven-3.6.1
  • IDEA

学习前需要掌握:

  • JDBC
  • MySQL
  • Java 基础
  • Maven
  • Junit

1.1、什么是 MyBatis ?

  • MyBatis 是一款优秀的持久层框架
  • 它支持定制化 SQL、存储过程以及高级映射。
  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。
  • MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
  • MyBatis 本是 apache 的一个开源项目iBatis, 2010年这个项目由 apache software foundation 迁移到了 google code,并且改名为MyBatis 。
  • 2013年11月迁移到Github。

如何获得 MyBatis?

1.2、持久化

数据持久化

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程
  • 内存:断电即失
  • 数据库(jdbc),io 文件持久化。
  • 生活:冷藏. 罐头。

为什么需要需要持久化?

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

1.3、持久层

Dao 层,Service 层,Controller 层…

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

1.4 为什么需要 Mybatis ?

  • 帮助程序猿将数据存入到数据库中。
  • 方便
  • 传统的 JDBC 代码太复杂了。简化。框架。自动化。
  • 不用 MyBatis 也可以。更容易上手。 技术没有高低之分
  • 优点:
    • 简单易学
    • 灵活
    • SQL 和代码的分离,提高了可维护性。
    • 提供映射标签,支持对象与数据库的 ORM 字段关系映射
    • 提供对象关系映射标签,支持对象关系组建维护
    • 提供 XML 标签,支持编写动态 SQL 。

最重要的一点:使用的人多!

2、环境搭建

2.1、搭建实验数据库

CREATE DATABASE `mybatis`;

USE `mybatis`;

DROP TABLE IF EXISTS `user`;

CREATE TABLE `user` (
`id` int(20) NOT NULL,
`name` varchar(30) DEFAULT NULL,
`pwd` varchar(30) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

insert  into `user`(`id`,`name`,`pwd`) values (1,'狂神','123456'),(2,'张三','abcdef'),(3,'李四','987654');

2.2、导入 Maven 相关依赖

<dependency>
   <groupId>org.mybatis</groupId>
   <artifactId>mybatis</artifactId>
   <version>3.5.2</version>
</dependency>
<dependency>
   <groupId>mysql</groupId>
   <artifactId>mysql-connector-java</artifactId>
   <version>5.1.47</version>
</dependency>

2.3、编写 MyBatis 核心配置文件

可以根据帮助文档来进行编写

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
       PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
       "//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=utf8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>
   <mappers>
       <mapper resource="com/kuang/dao/userMapper.xml"/>
   </mappers>
</configuration>

2.4、编写 MyBatis 工具类

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();
      }
  }

   //获取SqlSession连接
   public static SqlSession getSession(){
       return sqlSessionFactory.openSession();
  }

}

2.5、创建实体类

public class User {
   
   private int id;  //id
   private String name;   //姓名
   private String pwd;   //密码
   
   //构造,有参,无参
   //set/get
   //toString()
   
}

2.6、编写 Mapper 接口

import com.kuang.pojo.User;
import java.util.List;

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

2.7、编写 Mapper.xml 配置文件

注意 namespace 不要写错!

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "//mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.kuang.dao.UserMapper">
    
 <select id="selectUser" resultType="com.kuang.pojo.User">
  	select * from user
 </select>
    
</mapper>

2.8、编写测试类

**junit 包测试 **

public class MyTest {
   @Test
   public void selectUser() {
       SqlSession session = MybatisUtils.getSession();
       //方法一:
       //List<User> users = session.selectList("com.kuang.mapper.UserMapper.selectUser");
       
       //方法二:
       UserMapper mapper = session.getMapper(UserMapper.class);
       List<User> users = mapper.selectUser();

       for (User user: users){
           System.out.println(user);
      }
       session.close();
  }
}

出现的问题:

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

3、CRUD操作配置解析

3.1、select 标签

  • select标签是mybatis中最常用的标签之一

  • select语句有很多属性可以详细配置每一条SQL语句

    • SQL语句返回值类型。【完整的类名或者别名】
    • 传入SQL语句的参数类型 。【万能的Map,可以多尝试使用】
    • 命名空间中唯一的标识符
    • 接口中的方法名与映射文件中的SQL语句ID 一一对应
    • id
    • parameterType
    • resultType

练习 1 :根据 id 查询 用户

1.在 UserMapper 中添加对应方法

public interface UserMapper {
   //查询全部用户
   List<User> selectUser();
   //根据id查询用户
   User selectUserById(int id);
}

2.在UserMapper.xml中添加 select 语句

<select id="selectUserById" resultType="com.anti.pojo.User">
	select * from user where id = #{id}
</select>

3.在测试类中测试

@Test
public void tsetSelectUserById() {
   SqlSession session = MybatisUtils.getSession();  //获取SqlSession连接
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = mapper.selectUserById(1);
   System.out.println(user);
   session.close();
}

4.运行结果

运行结果

练习2:根据 密码 和名字 查询用户

方法一:直接在方法中传递参数

  • ​ 在接口方法中的参数前加 @Param 属性。
  • ​ SQL 语句编写的时候,直接取 @Param 中设置的值即可,不需要到单独设置参数类型。
//通过密码和名字查询用户
User selectUserByNP(@Param("username") String username,@Param("pwd") String pwd);


//mapper.xml
<select id="selectUserByNP" resultType="com.kuang.pojo.User">
    select * from user where name = #{username} and pwd = #{pwd}
</select>

方法二:万能Map

  • 在接口方法中,参数数直接传递Map。
User selectUserByNP2(Map<String,Object> map);
  • 在编写SQL语句的时候,需要传递参数类型 parameterType="map"
<select id="selectUserByNP2" parameterType="map" resultType="com.kuang.pojo.User">
    select * from user where name = #{username} and pwd = #{pwd}
</select>
  • 在使用方法的时候,Map 的 KEY 为 SQL 中取的值即可。
@Test
public void test03(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);
    HashMap<String, Object> map = new HashMap<String, Object>();
    map.put("username","张三");
    map.put("pwd","abcdef");
    User user = mapper.selectUserByNP2(map);
    System.out.println(user);
}

如果参数过多,我们可以考虑直接使用 Map 实现,如果参数比较少,直接传递参数即可。

3.2、insert 标签

我们一般使用 insert 标签进行插入操作,它的配置和 select 标签差不多.

练习1:增加一个用户

1.在 UserMapper 接口中添加对应的方法

//添加一个用户
int addUser(User user);

2、在UserMapper.xml中添加insert语句

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

3.测试

@Test
public void testAddUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = new User(5,"王五","zxcvbn");
   int i = mapper.addUser(user);
   System.out.println(i);
   session.commit(); //提交事务,重点!不写的话不会提交到数据库
   session.close();
}

注意点:增、删、改操作需要提交事务!

3.3、update 标签

我们一般使用update标签进行更新操作,它的配置和select标签差不多。

练习:修改用户的信息

1、同理,编写接口方法

//修改一个用户
int updateUser(User user);

2、编写对应的配置文件SQL

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

3、测试

@Test
public void testUpdateUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   User user = mapper.selectUserById(1);
   user.setPwd("asdfgh");
   int i = mapper.updateUser(user);
   System.out.println(i);
   session.commit(); //提交事务,重点!不写的话不会提交到数据库
   session.close();
}

3.4、delete 标签

需求:根据id删除一个用户

1、同理,编写接口方法

//根据id删除用户
int deleteUser(int id);

2、编写对应的配置文件SQL

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

3、测试

@Test
public void testDeleteUser() {
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   int i = mapper.deleteUser(5);
   System.out.println(i);
   session.commit(); //提交事务,重点!不写的话不会提交到数据库
   session.close();
}

小结:

  • 所有的 增、删、改操作都需要提交事务!
  • 所有的普通参数,尽量都写上 @Param 参数,尤其是多个参数时,必须写上!
  • 有时候根据业务的需求,可以考虑使用 map 传递参数!
  • 为了规范操作, 在 SQL 的配置文件中,我们尽量将 parameterTyperesultType 都写上!

3.5、模糊查询

第1种(推荐):在 Java代码中添加 SQL通配符。

List<User> users = mapper.selectLikeUser("%朱%");
<select id="selectLikeUser">
	select * from user where name like #{name}
</select>

第2种(不推荐):在 SQL 语句中拼接通配符,会引起 SQL 注入。

String name = "朱";
List<User> users = mapper.selectLikeUser(name);
<select id="selectLikeUser">
	select * from user where name like "%" #{name} "%"
</select>

4、配置解析

4.1、mybatis-config.xml 核心配置文件

  • MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。
  • 能配置的内容如下:
configuration(配置)
properties(属性)
settings(设置)
typeAliases(类型别名)
typeHandlers(类型处理器)
objectFactory(对象工厂)
plugins(插件)
environments(环境配置)
environment(环境变量)
transactionManager(事务管理器)
dataSource(数据源)
databaseIdProvider(数据库厂商标识)
mappers(映射器)
<!-- 注意元素节点的顺序!顺序不对会报错 -->

我们可以阅读 mybatis-config.xml 上面的 dtd 的头文件!

4.2、environments 元素

   <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=utf8"/>
               <property name="username" value="root"/>
               <property name="password" value="123456"/>
           </dataSource>
       </environment>
   </environments>

配置 MyBatis 的多套运行环境,将 SQL 映射到多个不同的数据库上,必须指定其中一个为默认运行环境(通过default指定)

  • 子元素节点:environment

    • dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。

    • 数据源是必须配置的。

    • 有三种内建的数据源类型

      type="[UNPOOLED|POOLED|JNDI]")
      
    • UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。

    • POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来 , 这是一种使得并发 Web 应用快速响应请求的流行处理方式。

    • JNDI:这个数据源的实现是为了能在如 Spring 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的引用。

    • 数据源也有很多第三方的实现,比如dbcp,c3p0,druid等等….

  • 子元素节点:transactionManager – [ 事务管理器 ]

<!-- 语法 -->
<transactionManager type="[ JDBC | MANAGED ]"/>

4.3、mappers 元素

mappers

  • 映射器 : 定义映射SQL语句文件
  • 既然 MyBatis 的行为其他元素已经配置完了,我们现在就要定义 SQL 映射语句了。但是首先我们需要告诉 MyBatis 到哪里去找到这些语句。Java 在自动查找这方面没有提供一个很好的方法,所以最佳的方式是告诉 MyBatis 到哪里去找映射文件。你可以使用相对于类路径的资源引用, 或完全限定资源定位符(包括 file:/// 的 URL),或类名和包名等。映射器是MyBatis中最核心的组件之一,在MyBatis 3之前,只支持xml映射器,即:所有的SQL语句都必须在xml文件中配置。而从MyBatis 3开始,还支持接口映射器,这种映射器方式允许以Java代码的方式注解定义SQL语句,非常简洁。
<!-- 使用相对于类路径的资源引用 -->
<mappers>
	<mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
	<mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>
<!--
使用映射器接口实现类的完全限定类名
需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
	<mapper class="org.mybatis.builder.AuthorMapper"/>
</mappers>
<!--
将包内的映射器接口实现全部注册为映射器
但是需要配置文件名称和接口名称一致,并且位于同一目录下
-->
<mappers>
	<package name="org.mybatis.builder"/>
</mappers>

mapper文件:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
       PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
       "//mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.UserMapper">
   
</mapper>

namespace 中文意思:命名空间,作用如下:

  • ​ namespace 的命名必须跟某个接口同名
  • ​ 接口中的方法与映射文件中 sql 语句 id 应该一 一对应

namespace 和子元素的 id 联合保证唯一 , 区别不同的mapper

绑定 DAO 接口

namespace 命名规则 : 包名 + 类名

4.4、properties 优化

数据库这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。具体的请参考官方文档

我们来优化我们的配置文件

第一步 ; 在resources资源目录下新建一个 db.properties

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
username=root
password=123456

第二步 : 将文件导入 properties 配置文件

<configuration>
   <!--导入properties文件-->
   <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="mapper/UserMapper.xml"/>
   </mappers>
</configuration>

5、typeAliases优化

类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

<!--配置别名,注意顺序-->
<typeAliases>
   <typeAlias type="com.anti.pojo.User" alias="User"/>
</typeAliases>

当这样配置时,User 可以用在任何使用 com.kuang.pojo.User 的地方。

也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

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

每一个在包 com.anti.pojo 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

若有注解,则别名为其注解值。见下面的例子:

@Alias("user")
public class User {
  ...
}

去官网查看一下Mybatis默认的一些类型别名: //mybatis.org/mybatis-3/zh/configuration.html#typeAliases

6、其他配置浏览

6.1、设置

设置(settings)相关 => 查看帮助文档

懒加载

日志实现

缓存开启关闭

一个配置完整的 settings 元素的示例如下:

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

6.2、类型处理器

  • 无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。
  • 你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。【了解即可】

6.3、对象工厂

  • MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
  • 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过有参构造方法来实例化。
  • 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。【了解即可】

7、生命周期和作用域

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

我们可以先画一个流程图,分析一下 Mybatis 的执行过程!

image-20201205171543934

7.1、作用域理解

  • SqlSessionFactoryBuilder 的作用在于创建 SqlSessionFactory,创建成功后,SqlSessionFactoryBuilder 就失去了作用,所以它只能存在于创建 SqlSessionFactory 的方法中,而不要让其长期存在。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。
  • SqlSessionFactory 可以被认为是一个数据库连接池,它的作用是创建 SqlSession 接口对象。因为 MyBatis 的本质就是 Java 对数据库的操作,所以 SqlSessionFactory 的生命周期存在于整个 MyBatis 的应用之中,所以一旦创建了 SqlSessionFactory,就要长期保存它,直至不再使用 MyBatis 应用,所以可以认为 SqlSessionFactory 的生命周期就等同于 MyBatis 的应用周期。
  • 由于 SqlSessionFactory 是一个对数据库的连接池,所以它占据着数据库的连接资源。如果创建多个 SqlSessionFactory,那么就存在多个数据库连接池,这样不利于对数据库资源的控制,也会导致数据库连接资源被消耗光,出现系统宕机等情况,所以尽量避免发生这样的情况。
  • 因此在一般的应用中我们往往希望 SqlSessionFactory 作为一个单例,让它在应用中被共享。所以说 SqlSessionFactory 的最佳作用域是应用作用域。
  • 如果说 SqlSessionFactory 相当于数据库连接池,那么 SqlSession 就相当于一个数据库连接(Connection 对象),你可以在一个事务里面执行多条 SQL,然后通过它的 commit、rollback 等方法,提交或者回滚事务。所以它应该存活在一个业务请求中,处理完整个请求后,应该关闭这条连接,让它归还给 SqlSessionFactory,否则数据库资源就很快被耗费精光,系统就会瘫痪,所以用 try…catch…finally… 语句来保证其正确关闭。
  • 所以 SqlSession 的最佳的作用域是请求或方法作用域。

image-20201205171555725

8、ReusltMap

8.1、在进行简单查询时,查询出来的值为 null

image-20201206133705016

可以看到查询出来的结果集中 username 属性为 null

8.2、原因分析

MyBatis 会根据这些查询的列名(会将列名转化为小写,数据库不区分大小写) , 去对应的实体类中查找相应列名的 set方法 设值 , 由于找不到 setUsernmae() , 所以 username 返回 null ; 【自动映射】

image-20201206133911746

8.3、解决方法

1.【不推荐】修改实体类的属性名,使其和数据库字段名一致。

image-20201206134056441

2.【不推荐】在 SQL 语句中使用别名对应实体类中的属性名。

image-20201206134740333

3.【推荐】使用在 xxMapper.xml` 中使用 ResultMap 进行结果集映射。

image-20201206135146896

8.4、数据库中,存在一对多,多对一的情况时

【多对一】的处理:

  • 多个学生对应一个老师
  • 如果对于学生这边,就是一个多对一的现象,即从学生这边关联一个老师。

image-20201206135928746

image-20201206141735576

image-20201206141721977

image-20201206141754594

【一对多】的处理:

image-20201206142906498

image-20201206142852540

image-20201206142917246

8.5、 总结:

1、关联 – association

2、集合 – collection

3、所以 association 是用于一对一和多对一,而 collection 是用于一对多的关系

4、JavaType ofType 都是用来指定对象类型的

  • JavaType 是用来指定 pojo 中属性的类型
  • ofType指定的是映射到 List 集合属性中 pojo 的类型。

注意说明:

1、保证SQL的可读性,尽量通俗易懂

2、根据实际要求,尽量编写性能更高的SQL语句

3、注意属性名和字段不一致的问题

4、注意一对多和多对一 中:字段和属性对应的问题

5、尽量使用Log4j,通过日志来查看自己的错误

9、 Log日志

9.1、思考

我们在测试 SQL 的时候,要是能够在控制台输出 SQL 的话,是不是就能够有更快的排错效率?

如果一个 数据库相关的操作出现了问题,我们可以根据输出的 SQL 语句快速排查问题。

对于以往的开发过程,我们会经常使用到debug模式来调节,跟踪我们的代码执行过程。但是现在使用 Mybatis 是基于接口,配置文件的源代码执行过程。因此,我们必须选择日志工具来作为我们开发,调节程序的工具。

Mybatis内置的日志工厂提供日志功能,具体的日志实现有以下几种工具:

  • SLF4J
  • Apache Commons Logging
  • Log4j 2
  • Log4j
  • JDK logging

具体选择哪个日志实现工具由 MyBatis 的内置日志工厂确定。它会使用最先找到的(按上文列举的顺序查找)。如果一个都未找到,日志功能就会被禁用。

9.2、标准日志实现

指定 MyBatis 应该使用哪个日志记录实现。如果此设置不存在,则会自动发现日志记录实现。

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

测试,可以看到控制台有大量的输出!我们可以通过这些输出来判断程序到底哪里出了Bug

Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
PooledDataSource forcefully closed/removed all connections.
Opening JDBC Connection
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
Created connection 355115154.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@152aa092]
==>  Preparing: select * from teacher where id = ? 
==> Parameters: 1(Integer)
<==    Columns: id, name
<==        Row: 1, 秦老师
====>  Preparing: select * from student 
====> Parameters: 
<====    Columns: id, name, tid
<====        Row: 1, 小明, 1
<====        Row: 2, 小红, 1
<====        Row: 3, 小张, 1
<====        Row: 4, 小李, 1
<====        Row: 5, 小王, 1
<====      Total: 5
<==      Total: 1
Teacher(id=null, name=秦老师, students=[Student(id=1, name=小明), Student(id=2, name=小红), Student(id=3, name=小张), Student(id=4, name=小李), Student(id=5, name=小王)])

9.3、使用 Log4j

简介:

  • Log4j是Apache的一个开源项目
  • 通过使用Log4j,我们可以控制日志信息输送的目的地:控制台,文本,GUI组件….
  • 我们也可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

使用步骤:

1、导入log4j的包

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

2、配置文件编写

#将等级为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=[%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

3、setting设置日志实现

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

输出结果:

[org.apache.ibatis.datasource.pooled.PooledDataSource]-Created connection 71706941.
[org.apache.ibatis.transaction.jdbc.JdbcTransaction]-Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@446293d]
[com.anti.dao.TeacherMapper.getTeacherById]-==>  Preparing: select * from teacher where id = ? 
[com.anti.dao.TeacherMapper.getTeacherById]-==> Parameters: 1(Integer)
[com.anti.dao.TeacherMapper.student]-====>  Preparing: select * from student 
[com.anti.dao.TeacherMapper.student]-====> Parameters: 
[com.anti.dao.TeacherMapper.student]-<====      Total: 5
[com.anti.dao.TeacherMapper.getTeacherById]-<==      Total: 1
Teacher(id=null, name=秦老师, students=[Student(id=1, name=小明), Student(id=2, name=小红), Student(id=3, name=小张), Student(id=4, name=小李), Student(id=5, name=小王)])

image-20201206145306565

10、分页

在学习 MyBatis 等持久层框架的时候,会经常对数据进行增删改查操作,使用最多的是对数据库进行查询操作,如果查询大量数据的时候,我们往往使用分页进行查询,也就是每次处理小部分数据,这样对数据库压力就在可控范围内。

使用Limit实现分页

#语法
SELECT * FROM table LIMIT page,pageSize

page = 当前页数 = (page-1)*pageSize
pageSize = 一页多少条数据

UserMapper.java

public interface UserMapper {
    //选择全部用户实现分页
    List<User> selectUser(Map<String,Integer> map);
}

UserMapper.xml

<select id="selectUser" parameterType="map" resultType="com.anti.pojo.User">
    select * from user limit #{page},#{pageSize}
</select>

MyTest.java

@Test
public void test01(){
    SqlSession session = MybatisUtils.getSession();
    UserMapper mapper = session.getMapper(UserMapper.class);

    HashMap<String, Integer> map = new HashMap<String, Integer>();
    int page = 1; //第几页
    int limit = 10; //每页多少数据
    map.put("page",(page-1) * limit);
    map.put("limit",limit);

    List<User> users = mapper.selectUser(map);
    for (User user : users) {
        System.out.println(user);
    }
    session.close();
}

结果 :

==>  Preparing: select * from user limit ?,? 
==> Parameters: 0(Integer), 10(Integer)
<==    Columns: uid, name, gender, birthday, dept, cno, address, phone, remark, password, type
<==        Row: 999, 管理员, M, 2020/09/02, AAA, 0, AAA, null, null, 123, 0
<==        Row: 10101, 怡香, M, 2020/02/08, Accounting, 1, Cameroon, 823-954-4217, null, 1, 1
<==        Row: 10102, 唯枫, M, 2020/01/25, Accounting, 2, Palestinian Territory, 978-827-9275, null, 2, 1
<==        Row: 10103, 海程, M, 2020/09/05, Human Resources, 3, Thailand, 978-712-9955, null, 3, 1
<==        Row: 10104, 琪煜, M, 2020/10/07, Accounting, 4, Palestinian Territory, 730-153-0025, null, 4, 1
<==        Row: 10105, 彦军, F, 2020/11/05, Services, 5, China, 504-460-1356, null, 5, 1
<==        Row: 10106, 宇涵, F, 2020/11/08, Product Management, 6, Argentina, 252-143-6848, null, 6, 1
<==        Row: 10107, 辰华, M, 2019/11/25, Business Development, 7, Philippines, 884-928-7856, null, 7, 1
<==        Row: 10108, 晓烽, M, 2020/08/05, Engineering, 8, Philippines, 152-366-5638, null, 8, 1
<==        Row: 10109, 尹智, F, 2020/01/12, Human Resources, 9, Argentina, 803-602-3704, null, 9, 1
<==      Total: 10

11、使用注解开发

MyBatis最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到MyBatis 3提供了新的基于注解的配置。不幸的是,java 注解的的表达力和灵活性十分有限。

最强大的 MyBatis 映射并不能用注解来构建

  • 注解主要分成 :
    • @select ()
    • @update ()
    • @Insert ()
    • @delete ()

注意:利用注解开发就不需要mapper.xml映射文件了 .

1.在接口中添加注解

public interface UserMapper {
	//查询全部用户
    @Select("select * from user")
    List<User> getUsers();
}

2.在mybatis的核心配置文件中注入

<!--使用class绑定接口-->
<mappers>
   <mapper class="com.anti.mapper.UserMapper"/>
</mappers>

3.测试

@Test
public void test01(){
    UserMapper mapper = MybatisUtils.getSession().getMapper(UserMapper.class);
    List<User> users = mapper.getUsers();
    for (User user : users) {
        System.out.println(user);
    }
}

4.结果

Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3c0be339]
==>  Preparing: select * from user 
==> Parameters: 
<==    Columns: id, name, pwd
<==        Row: 1, 狂神, 123456
<==        Row: 2, 张三, abcdef
<==        Row: 3, 李四, 987654
<==        Row: 5, 王五, zxcvbn
<==      Total: 4
User(id=1, name=狂神, pwd=123456)
User(id=2, name=张三, pwd=abcdef)
User(id=3, name=李四, pwd=987654)
User(id=5, name=王五, pwd=zxcvbn)

Process finished with exit code 0

12、动态SQL

12.1、if 语句

<!--需求1:
根据作者名字和博客名字来查询博客!
如果作者名字为空,那么只根据博客名字查询,反之,则根据作者名来查询
select * from blog where title = #{title} and author = #{author}
-->
<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog where
   <if test="title != null">
      title = #{title}
   </if>
   <if test="author != null">
      and author = #{author}
   </if>
</select>

这样写我们可以看到,如果 author 等于 null,那么查询语句为 select * from user where title=#{title},但是如果 title 为空呢?那么查询语句为 select * from user where and author=#{author},这是错误的 SQL 语句,如何解决呢?请看下面的 where 语句!

12.2、where 语句

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

这个“where”标签会知道如果它包含的标签中有返回值的话,它就插入一个‘where’。此外,如果标签返回的内容是以AND 或OR 开头的,则它会剔除掉。

12.3、set 语句

同理,上面的对于查询 SQL 语句包含 where 关键字,如果在进行更新操作的时候,含有 set 关键词,我们怎么处理呢?

<!--注意set是用的逗号隔开-->
<update id="updateBlog" parameterType="map">
  update blog
     <set>
         <if test="title != null">
            title = #{title},
         </if>
         <if test="author != null">
            author = #{author}
         </if>
     </set>
  where id = #{id};
</update>

12.4、choose 语句

有时候,我们不想用到所有的查询条件,只想选择其中的一个,查询条件有一个满足即可,使用 choose 标签可以解决此类问题,类似于 Java 的 switch 语句。

choose 与 if 的区别:

  • if 如果条件都满足会带出所有满足条件的语句
  • choose 只会带出最先满足的条件的语句
  • choose 如果没有满足的条件会执行 otherwise 语句
<select id="queryBlogChoose" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <choose>
           <when test="title != null">
                title = #{title}
           </when>
           <when test="author != null">
              and author = #{author}
           </when>
           <otherwise>
              and views = #{views}
           </otherwise>
       </choose>
   </where>
</select>

12.5、SQL 片段

有时候可能某个 sql 语句我们用的特别多,为了增加代码的重用性,简化代码,我们需要将这些代码抽取出来,然后使用时直接调用。

提取SQL片段:

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

引用SQL片段:

<select id="queryBlogIf" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!-- 引用 sql 片段,如果refid 指定的不在本文件中,那么需要在前面加上 namespace -->
       <include refid="if-title-author"></include>
       <!-- 在这里还可以引用其他的 sql 片段 -->
   </where>
</select>

注意:

  1. 最好基于 单表来定义 sql 片段,提高片段的可重用性
  2. 在 sql 片段中不要包括 where

12.6、foreach 语句

需求:我们需要查询 blog 表中 id 分别为1,2,3的博客信息

<select id="queryBlogForeach" parameterType="map" resultType="blog">
  select * from blog
   <where>
       <!--
       collection:指定输入对象中的集合属性
       item:每次遍历生成的对象
       open:开始遍历时的拼接字符串
       close:结束时拼接的字符串
       separator:遍历对象之间需要拼接的字符串
       select * from blog where 1=1 and (id=1 or id=2 or id=3)
     -->
       <foreach collection="ids"  item="id" open="and (" close=")" separator="or">
          id=#{id}
       </foreach>
   </where>
</select>

小结:

其实动态 sql 语句的编写往往就是一个拼接的问题,为了保证拼接准确,我们最好首先要写原生的 sql 语句出来,然后在通过 mybatis 动态sql 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。

13、缓存

1、什么是缓存 [ Cache ]?

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

2、为什么使用缓存?

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

3、什么样的数据能使用缓存?

  • 经常查询并且不经常改变的数据。

13.1、Mybatis缓存

  • MyBatis包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

  • MyBatis系统中默认定义了两级缓存:一级缓存二级缓存

    • 默认情况下,只有一级缓存开启。(SqlSession级别的缓存,也称为本地缓存)

    • 二级缓存需要手动开启和配置,他是基于namespace级别的缓存。

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

13.2、一级缓存

一级缓存也叫本地缓存:

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。

  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

1.在mybatis中加入日志,方便测试结果

2.编写接口方法

//根据id查询用户
User queryUserById(@Param("id") int id);

3.接口对应的Mapper文件

<select id="queryUserById" resultType="user">
  select * from user where id = #{id}
</select>

4.测试

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
}

5.结果分析

image-20201206213629212

13.3、一级缓存失效的四种情况:

一级缓存是SqlSession级别的缓存,是一直开启的,我们关闭不了它;

一级缓存失效情况:没有使用到当前的一级缓存,效果就是,还需要再向数据库中发起一次查询请求!

1.sqlSession不同

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
   session2.close();
}

观察结果:发现发送了两条SQL语句!

结论:每个sqlSession中的缓存相互独立

2.sqlSession相同,查询条件不同

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   User user2 = mapper2.queryUserById(2);
   System.out.println(user2);
   System.out.println(user==user2);

   session.close();
}

观察结果:发现发送了两条SQL语句!很正常的理解

结论:当前缓存中,不存在这个数据

3.sqlSession相同,两次查询之间执行了增删改操作!

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);

   HashMap map = new HashMap();
   map.put("name","kuangshen");
   map.put("id",4);
   mapper.updateUser(map);

   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

   System.out.println(user==user2);

   session.close();
}

观察结果:查询在中间执行了增删改操作后,重新执行了

结论:因为增删改操作可能会对当前数据产生影响

4.sqlSession相同,手动清除一级缓存

@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   UserMapper mapper = session.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);

   session.clearCache();//手动清除缓存

   User user2 = mapper.queryUserById(1);
   System.out.println(user2);

   System.out.println(user==user2);

   session.close();
}

一级缓存就是一个map

13.4、二级缓存

1.开启全局缓存 【mybatis-config.xml】

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

2.去每个mapper.xml中配置使用二级缓存,这个配置非常简单;【xxxMapper.xml】

<cache/>

官方示例=====>查看官方文档
<cache
 eviction="FIFO"
 flushInterval="60000"
 size="512"
 readOnly="true"
/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

3.代码测试

  • 所有的实体类先实现序列化接口
  • 测试代码
@Test
public void testQueryUserById(){
   SqlSession session = MybatisUtils.getSession();
   SqlSession session2 = MybatisUtils.getSession();

   UserMapper mapper = session.getMapper(UserMapper.class);
   UserMapper mapper2 = session2.getMapper(UserMapper.class);

   User user = mapper.queryUserById(1);
   System.out.println(user);
   session.close();

   User user2 = mapper2.queryUserById(1);
   System.out.println(user2);
   System.out.println(user==user2);

   session2.close();
}

image-20201206214729672

结论:

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据
  • 查出的数据都会被默认先放在一级缓存中
  • 只有会话提交或者关闭以后,一级缓存中的数据才会转到二级缓存中

缓存原理图:

image-20201206214908158

13.5、第三方缓存实现 EhCache

image-20210130005613005

Ehcache是一种广泛使用的 java 分布式缓存,用于通用缓存;

1.要在应用程序中使用Ehcache,需要引入依赖的 jar 包

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

2.在mapper.xml中使用对应的缓存即可

<mapper namespace = “org.acme.FooMapper” >
   <cache type = “org.mybatis.caches.ehcache.EhcacheCache” />
</mapper>

3.编写ehcache.xml文件,如果在加载时未找到/ehcache.xml资源或出现问题,则将使用默认配置。

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="//www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="//ehcache.org/ehcache.xsd"
        updateCheck="false">
   <!--
      diskStore:为缓存路径,ehcache分为内存和磁盘两级,此属性定义磁盘的缓存位置。参数解释如下:
      user.home – 用户主目录
      user.dir – 用户当前工作目录
      java.io.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"/>
   <!--
      defaultCache:默认缓存策略,当ehcache找不到定义的缓存时,则使用这个缓存策略。只能定义一个。
    -->
   <!--
     name:缓存名称。
     maxElementsInMemory:缓存最大数目
     maxElementsOnDisk:硬盘最大缓存个数。
     eternal:对象是否永久有效,一但设置了,timeout将不起作用。
     overflowToDisk:是否保存到磁盘,当系统当机时
     timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
     timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
     diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
     diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
     diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
     memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
     clearOnFlush:内存数量最大时是否清除。
     memoryStoreEvictionPolicy:可选策略有:LRU(最近最少使用,默认策略)、FIFO(先进先出)、LFU(最少访问次数)。
     FIFO,first in first out,这个是大家最熟的,先进先出。
     LFU, Less Frequently Used,就是上面例子中使用的策略,直白一点就是讲一直以来最少被使用的。如上面所讲,缓存的元素有一个hit属性,hit值最小的将会被清出缓存。
     LRU,Least Recently Used,最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清出缓存。
  -->

</ehcache>

14、Mybatis详细的执行流程

img