MyBatis学习笔记

一、简介

image-20210129225418682

环境说明:html

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

学习前须要掌握:java

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

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

1.二、持久化

数据持久化shell

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

为何须要须要持久化?数据库

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

1.三、持久层

Dao 层,Service 层,Controller 层…apache

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

1.4 为何须要 Mybatis ?

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

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

二、环境搭建

2.一、搭建实验数据库

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.二、导入 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.三、编写 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>
   <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.四、编写 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.五、建立实体类

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

2.六、编写 Mapper 接口

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

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

2.七、编写 Mapper.xml 配置文件

注意 namespace 不要写错!

<?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.kuang.dao.UserMapper">
    
 <select id="selectUser" resultType="com.kuang.pojo.User">
  	select * from user
 </select>
    
</mapper>

2.八、编写测试类

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

出现的问题:

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

三、CRUD操做配置解析

3.一、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.二、insert 标签

咱们通常使用 insert 标签进行插入操做,它的配置和 select 标签差很少.

练习1:增长一个用户

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

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

二、在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.三、update 标签

咱们通常使用update标签进行更新操做,它的配置和select标签差很少。

练习:修改用户的信息

一、同理,编写接口方法

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

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

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

三、测试

@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.四、delete 标签

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

一、同理,编写接口方法

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

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

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

三、测试

@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.五、模糊查询

第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.一、mybatis-config.xml 核心配置文件

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

咱们能够阅读 mybatis-config.xml 上面的 dtd 的头文件!

4.二、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.三、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"
       "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kuang.mapper.UserMapper">
   
</mapper>

namespace 中文意思:命名空间,做用以下:

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

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

绑定 DAO 接口

namespace 命名规则 : 包名 + 类名

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>

五、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默认的一些类型别名: https://mybatis.org/mybatis-3/zh/configuration.html#typeAliases

六、其余配置浏览

6.一、设置

设置(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.二、类型处理器

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

6.三、对象工厂

  • MyBatis 每次建立结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。
  • 默认的对象工厂须要作的仅仅是实例化目标类,要么经过默认构造方法,要么在参数映射存在的时候经过有参构造方法来实例化。
  • 若是想覆盖对象工厂的默认行为,则能够经过建立本身的对象工厂来实现。【了解便可】

七、生命周期和做用域

理解咱们目前已经讨论过的不一样做用域和生命周期类是相当重要的,由于错误的使用会致使很是严重的并发问题。

咱们能够先画一个流程图,分析一下 Mybatis 的执行过程!

image-20201205171543934

7.一、做用域理解

  • 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

八、ReusltMap

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

image-20201206133705016

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

8.二、缘由分析

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

image-20201206133911746

8.三、解决方法

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

image-20201206134056441

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

image-20201206134740333

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

image-20201206135146896

8.四、数据库中,存在一对多,多对一的状况时

【多对一】的处理:

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

image-20201206135928746

image-20201206141735576

image-20201206141721977

image-20201206141754594

【一对多】的处理:

image-20201206142906498

image-20201206142852540

image-20201206142917246

8.五、 总结:

一、关联 - association

二、集合 - collection

三、因此 association 是用于一对一和多对一,而 collection 是用于一对多的关系

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

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

注意说明:

一、保证SQL的可读性,尽可能通俗易懂

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

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

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

五、尽可能使用Log4j,经过日志来查看本身的错误

九、 Log日志

9.一、思考

咱们在测试 SQL 的时候,要是可以在控制台输出 SQL 的话,是否是就可以有更快的排错效率?

若是一个 数据库相关的操做出现了问题,咱们能够根据输出的 SQL 语句快速排查问题。

对于以往的开发过程,咱们会常用到debug模式来调节,跟踪咱们的代码执行过程。可是如今使用 Mybatis 是基于接口,配置文件的源代码执行过程。所以,咱们必须选择日志工具来做为咱们开发,调节程序的工具。

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

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

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

9.二、标准日志实现

指定 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.三、使用 Log4j

简介:

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

使用步骤:

一、导入log4j的包

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

二、配置文件编写

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

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

十、分页

在学习 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

十一、使用注解开发

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

十二、动态SQL

12.一、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.二、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.三、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.四、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.五、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.六、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 对照着改,防止出错。多在实践中使用才是熟练掌握它的技巧。

1三、缓存

一、什么是缓存 [ Cache ]?

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

二、为何使用缓存?

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

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

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

13.一、Mybatis缓存

  • MyBatis包含一个很是强大的查询缓存特性,它能够很是方便地定制和配置缓存。缓存能够极大的提高查询效率。

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

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

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

    • 为了提升扩展性,MyBatis定义了缓存接口Cache。咱们能够经过实现Cache接口来自定义二级缓存

13.二、一级缓存

一级缓存也叫本地缓存:

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

  • 之后若是须要获取相同的数据,直接从缓存中拿,没必须再去查询数据库;

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.三、一级缓存失效的四种状况:

一级缓存是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.四、二级缓存

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.五、第三方缓存实现 EhCache

image-20210130005613005

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

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

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

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="http://www.w3.org/2001/XMLSchema-instance"
        xsi:noNamespaceSchemaLocation="http://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>

1四、Mybatis详细的执行流程

img

相关文章
相关标签/搜索