Java程序员从笨鸟到菜鸟(十五)MyBatis 相关介绍

一、MyBatis 介绍

1. 什么是 mybatis?

mybatis 是一个优秀的持久层框架,对 JDBC 操作数据库的过程进行了封装,只需要关注 SQL 本身,mybatis 通过 xml 或者 注解的方式,将要执行的各种 sql 语句配置,并通过 java 对象和 statement 语句映射成最终的 sql 语句,最后由 mybatis 框架执行 sql 语句,并将结果映射成 Java 对象返回

2. 工作原理?

mybatis 通过配置文件创建 sqlSessionFactory,sqlSessionFactory 根据配置文件,配置文件来源于两个方面:xml 文件,Java 注解,获取 sqlSession, sqlSession 包含了执行 sql 语句的所有方法,可以直接通过 sqlSession 运行 sql语句,完成对数据的增删改查和事务提交工作,之后再关闭 sqlSession.

3. 工作流程

mapper 接口:

xml 中 namespace 中的是接口类的全名;

  • 接口中的方法名是 xml 文件中的 sql 语句中的 id 值
  • 接口中的方法参数就是传递给 sql 的参数
  • mapper 接口的工作原理:mybatis 会使用 jdk 动态代理的方式为 mapper 接口创建 proxy 对象,代理对象会拦截接口中的方法,转而执行 sql 语句,然后将执行结果封装返回

4. mybatis 解决的问题

  1. 使用数据库连接池管理连接,避免了频繁创建、关闭连接,浪费资源,影响性能问题
  2. 使用 xml 管理 sql 语句,让 java 代码和 sql 语句分离,使得代码更容易维护
  3. 解决了 sql 语句参数不定的问题,xml 中可以通过 where 条件决定 sql 语句的条件参数,通过 parameterType 定义输入参数的类型
  4. mybatis 自动将结果集封装成 java 对象,通过 statement 的 resultType 定义输出的类型

 

二、理解MyBatis

MyBatis支持普通的SQL查询、存储过程和高级映射的优秀持久层框架,几乎消除了所有的JDBC代码和参数的手工设置以及对结果集的检索。MyBatis可以使用简单的XML 或注解用于配置和原始映射,将接口和Java的POJO(普通Java对象)映射成数据库中的记录

三、 MyBati缓存

分为一级缓存二级缓存

1.一级缓存:基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session, 当 Session flush 或 close 之后,该 Session 的所有 Cache 就将清空,作用域同一个SqlSession,第一次查询会访问数据库,第二次查询直接从缓存中读取,当一个SelSession 结束之后,SqlSession 的一级缓存也不存在了,默认开启 SqlSession 的一级缓存

2.二级缓存:与一级缓存机制相同,默认采用 PerpetualCache 的 HashMap 本地缓存,不同的是存储作用域为 Mapper(namespace),并且可以自定义存储源,多个SqlSession 去执行相同的 SQL 语句,得到的数据会放在二级缓存中,多个SqlSession 共享数据,默认没有开启二级缓存,需手动开启

在MyBatis3.4.4版不能直接使用#{0}要使用 #{arg0}

代码测试:

1.配置 log4j.properties 

# Global logging configuration
log4j.rootLogger=DEBUG, stdout
# Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%5p [%t] - %m%n

2. 使用单例模式创建SqlSessionFactory

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;

/**
 * create by tan on 2018/6/27
 * 用单例模式创建SqlSessionFactory
 **/
public class MybatisUtil {
    private static SqlSessionFactory sqlSessionFactory = null;
    static {
        String resource = "MyBatis.xml";
        InputStream inputStream = null;
        try {
            inputStream = Resources.getResourceAsStream(resource);
        } catch (IOException e) {
            e.printStackTrace();
        }
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

 一级缓存

testCache()方法

public void testCache() {
        SqlSession session = MybatisUtil.getSqlSession();
        UserDAO userDAO = session.getMapper(UserDAO.class);
        // 第一次查询
        User user = userDAO.getUserById(80);

        // 第二次查询
        User user1 = userDAO.getUserById(80);
        System.out.println(user.equals(user1)); // 返回true,第二次查询是从缓存中取值
        session.close();
    }

 

运行结果:

 

DEBUG [main] - Cache Hit Ratio [ssm.dao.UserDAO]: 0.0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 33105141.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1f924f5]
DEBUG [main] - ==>  Preparing: select * from user where id = ?; 
DEBUG [main] - ==> Parameters: 80(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Cache Hit Ratio [ssm.dao.UserDAO]: 0.0
true

分析:第一次查询从数据库中取数据,打印了SQL语句,第二次直接从缓存中读取,返回的是同一个对象,返回true

对比分析:采用不同的SqlSession,修改testCache()代码如下:

public void testCache() {
        SqlSession session = MybatisUtil.getSqlSession();
        UserDAO userDAO = session.getMapper(UserDAO.class);
        // 第一次查询
        User user = userDAO.getUserById(80);
        session.close();

        // 第二次查询,不同的SqlSession
        session = MybatisUtil.getSqlSession();
        userDAO = session.getMapper(UserDAO.class);
        User user1 = userDAO.getUserById(80);
        
        System.out.println(user.equals(user1)); // 返回true,第二次查询是从缓存中取值
        session.close();
    }

运行结果:

DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@a35978]
DEBUG [main] - ==>  Preparing: select * from user where id = ?; // 第一次查询,访问数据库
DEBUG [main] - ==> Parameters: 80(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@a35978]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@a35978] // 关闭 session
DEBUG [main] - Returned connection 10705272 to pool.
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 10705272 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@a35978]
DEBUG [main] - ==>  Preparing: select * from user where id = ?; // 第二次查询,访问数据库
DEBUG [main] - ==> Parameters: 80(Integer)
DEBUG [main] - <==      Total: 1

分析:不同的 SqlSession,两次查询都访问了数据库,多个SqlSession 不能共享缓存

 

testCache2()方法:有删除和提交操作

@Test
    public void testCache2() {
        SqlSession session = MybatisUtil.getSqlSession();
        UserDAO userDAO = session.getMapper(UserDAO.class);
        // 第一次查询
        User user = userDAO.getUserById(80);

        // 执行 delete 和 commit 操作
        userDAO.deleteUser(88);
        session.commit();

        // 第二次查询
        User user1 = userDAO.getUserById(80);
        System.out.println(user.equals(user1)); // 返回true,第二次查询是从缓存中取值
        session.close();
    }

运行结果:

DEBUG [main] - Cache Hit Ratio [ssm.dao.UserDAO]: 0.0 // 第一次拿缓存,没有缓存,命中率为 0 
DEBUG [main] - Opening JDBC Connection // 打开数据库连接
DEBUG [main] - Created connection 19112467.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - ==>  Preparing: select * from user where id = ?; // 第一次查询,访问数据库
DEBUG [main] - ==> Parameters: 80(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - ==>  Preparing: delete from user where id = ?; // 执行删除、提交操作
DEBUG [main] - ==> Parameters: 88(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Cache Hit Ratio [ssm.dao.UserDAO]: 0.0
DEBUG [main] - ==>  Preparing: select * from user where id = ?; // 第二次查询,访问数据库
DEBUG [main] - ==> Parameters: 80(Integer)
DEBUG [main] - <==      Total: 1
false

在删除和提交操作之后,再次执行查询操作,查询了数据库2次,因为删除操作之后清空了缓存,所以返回的结果也不是同一个,返回false

 

二级缓存:

实体类需要实现 Serializable 接口,为了将缓存数据取出执行反序列化操作

配置userMapper.xml

开启缓存:

// 开启二级缓存
<cache/>

testCache()

public void testCache() {
        // 获取 SqlSession 对象
        SqlSession session = MybatisUtil.getSqlSession();
        UserDAO userDAO = session.getMapper(UserDAO.class);
        User user = userDAO.getUserById(80);
        //System.out.println("user=" + user);
        session.close();

        // 再次获取 SqlSession 对象
        session = MybatisUtil.getSqlSession();
        userDAO = session.getMapper(UserDAO.class);
        User user2 = userDAO.getUserById(80);
        //System.out.println("user2=" + user2);
        session.close();

        // 执行删除、提交操作
        session = MybatisUtil.getSqlSession();
        userDAO = session.getMapper(UserDAO.class);
        userDAO.deleteUser(87);
        session.commit();
        session.close();

        // 第三次获取 SqlSession 对象
        session = MybatisUtil.getSqlSession();
        userDAO = session.getMapper(UserDAO.class);
        User user3 = userDAO.getUserById(80);
        //System.out.println("user3=" + user2);
        session.close();
    }

运行结果:

DEBUG [main] - Cache Hit Ratio [ssm.dao.UserDAO]: 0.0 // 第一次拿缓存,没有缓存,缓存命中率为0
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Created connection 19112467.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - ==>  Preparing: select * from user where id = ?; // 第一次执行查询语句,访问数据库
DEBUG [main] - ==> Parameters: 80(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Returned connection 19112467 to pool.
DEBUG [main] - Cache Hit Ratio [ssm.dao.UserDAO]: 0.5 // 第二次拿缓存,之前SqlSession有缓存,缓存命中率为0.5
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 19112467 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - ==>  Preparing: delete from user where id = ?; // 执行删除、提交操作,会清空缓存二级缓存中的数据
DEBUG [main] - ==> Parameters: 87(Integer)
DEBUG [main] - <==    Updates: 1
DEBUG [main] - Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Returned connection 19112467 to pool.
DEBUG [main] - Cache Hit Ratio [ssm.dao.UserDAO]: 0.3333333333333333 // 第三次拿缓存,缓存刚被清空,所以缓存命中率为1/3,共拿了三次,就第二次成功了
DEBUG [main] - Opening JDBC Connection
DEBUG [main] - Checked out connection 19112467 from pool.
DEBUG [main] - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - ==>  Preparing: select * from user where id = ?; // 第三次拿不到缓存,执行查询语句,访问数据库
DEBUG [main] - ==> Parameters: 80(Integer)
DEBUG [main] - <==      Total: 1
DEBUG [main] - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@123a213]
DEBUG [main] - Returned connection 19112467 to pool.

分析:多个 SqlSession 可以共享缓存中的数据,不会再次访问数据库,当执行insert、update、delete 操作时,会刷新缓存

参考文章https://blog.csdn.net/u010858605/article/details/70906617?locationNum=2&fps=1 非常感谢作者

四、MyBatis 批量操作

批量插入insert:

  1. 普通 for 循环
  2. mybatis batch 模式插入
  3. foreach 方式插入

五、相关面试题

1.mybatis #{} 以及 ${}

动态 sql 是 mybatis 的主要特征之一,在 mapper 中定义的参数传到 xml 中,在查询之前 mybatis 会对其进行动态解析,mybatis 为我们提供了两种支持动态 sql 的语法: #{} 以及 ${}

#{}:标志占位符,向占位符传入参数,mybatis 会自动将 java 类型转换成 jdbc 类型,预编译时,不传入参数,而是传入占位符 ?,不容易出现 sql 注入

${}:标志 sql 拼接,通过 ${} 接收参数,将参数内容不加修饰的传入 sql,容易 sql 注入,不安全

版权声明:欢迎转载, 转载请保留原文链接。https://mp.csdn.net/postedit/80230556

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值