栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

MyBatis笔记

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

MyBatis笔记

文章目录
      • 第一部分 自定义持久层框架
        • 1.1 原生 jdbc 存在问题
        • 1.2 自定义持久层框架
        • 1.3 具体实现
      • 第二部分:Mybatis 相关概念
        • 2.1 对象/关系数据库映射(ORM)
        • 2.2 Mybatis 简介
        • 2.3 Mybatis 历史
        • 2.4 Mybatis 优势
      • 第三部分:Mybatis 基本应用
        • 3.1 快速入门
          • 3.1.1 开发步骤:
          • 3.1.2 环境搭建:
        • 3.2 核心配置文件分析
          • 3.2,1 MyBatis 的映射文件概述
          • 3.2.2 代理开发方式
          • 3.2.3 动态 sql 语句
          • 3.2.4 延迟加载
      • 第四部分:Mybatis 复杂映射开发
        • 1.1 一对一查询
        • 1.2 一对多查询
        • 1.3 多对多查询
      • 第五部分:Mybatis 注解开发
        • 1.1 MyBatis 的常用注解
        • 1.2 MyBatis 的常用注解实现
      • 第六部分:Mybatis 缓存
        • 1.1 一级缓存
        • 1.2 二级缓存
        • 1.3 二级缓存整合 redis
      • 第七部分:Mybatis 插件
        • 1.1 插件简介
        • 1.2 Mybatis 插件介绍
        • 1.3 Mybatis 插件原理
        • 1.4 自定义插件
        • 1.5 pageHelper 分页插件
        • 1.6 通用 mapper
      • 第八部分:Mybatis 架构原理
        • 1.1 架构设计
        • 1.2 主要构件及其相互关系
        • 1.3 总体流程
        • 1.4 重点介绍
      • 总结

第一部分 自定义持久层框架 1.1 原生 jdbc 存在问题
  1. 频繁的连接释放数据库
  2. sql 语句、参数赋值、结果集参数存在硬编码问题
  3. 手动封装返回结果集
1.2 自定义持久层框架

设计思路

  1. 由数据库配置文件解析获取数据库对象通过连接池获取连接池对象 datasource 再根据 datasource 获取连接 connection,根据 connection 获取 prepareStatement 进而执行 SQL 封装返回结果
  2. 由 sqlMapper.xml 获取每一个执行的的 SQL 语句、ID、参数、返回值、namespace,根据 namespace.id 作为封装的 mappedStatement 对象的集合 key
  3. 通过 configuration 对象封装 mappedStatement 的 map 集合,通过数据库信息封装数据源 datasource
  4. 开始 JDBC 六大步 第一步根据 configuration 封装的 数据源 datasource 获取连接 connection
  5. 获取 prepareStatement 对象
  6. 根据 mappedStatement 获取 SQL 语句 参数等
  7. 处理获取得到的 SQL 语句占位符 #{id} 则把 ID 存放到一个 list 里面之后替换成?
  8. 遍历 SQL 占位符得到的 list ,根据反射获取参数中该属性的值进行对应赋值
  9. 执行处理过的 SQL 语句,获取结果集
  10. 根据 mappedStatement 获取返回值类型反射获取对象,根据 metadata 进行内省(反射)赋予对应列名和属性值赋予对象封装集合
  11. 进行测试执行,发现代码重复和 statementid 的硬编码问题
  12. 通过 jdk 动态代理来执行,因为无法得到 statementid 需要 namespace 的值改为 dao 接口名,方法名和 SQL 的 ID 名保持一致

设计步骤

1.3 具体实现

sql 修改

  1. 通过 boundSql 对 sql 进行转化,把参数#{}转变为?
  2. 把#{}中的参数以对象的形式封装在 List 当中

设置参数

  1. 通过 mappedStatement 获取参数类型的反射对象 paramtertypeClass
  2. 遍历 sql 转化的时候存储的参数值的 parameterMappingList
  3. 获取 declaredField =paramtertypeClass.getDeclaredField(content);
  4. 获取参数对应值 o = declaredField.get(params[0]);
  5. 顺序赋值给 preparedStatement.setObject(i + 1, o);
     // 4. 设置参数
            //获取到了参数的全路径
             String paramterType = mappedStatement.getParamterType();
             Class paramtertypeClass = getClassType(paramterType);
                List parameterMappingList = boundSql.getParameterMappingList();
                for (int i = 0; i < parameterMappingList.size(); i++) {
                    ParameterMapping parameterMapping = parameterMappingList.get(i);
                    String content = parameterMapping.getContent();
                    //反射
                    Field declaredField = paramtertypeClass.getDeclaredField(content);
                    //暴力访问
                    declaredField.setAccessible(true);
                    Object o = declaredField.get(params[0]);
                    preparedStatement.setObject(i + 1, o);
                }
    

封装结果集

  1. 获取数据库表列名的集合 resultSet.getmetaData();
  2. 遍历获取每个列名和其对应的值
    // 字段名
    String columnName = metaData.getColumnName(i);
    // 字段的值
    Object value = resultSet.getObject(columnName);
    
  3. 使用反射或内省将值写入到返回的对象中
    //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
    PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
    Method writeMethod = propertyDescriptor.getWriteMethod();
    writeMethod.invoke(o,value);
    
  4. 将返回对象添加到结果集 list 当中

dao 的实现类存在问题

  1. dao 的实现类代码重复,每次都需获取 sqlSession
  2. statementId 存在硬编码问题

使用 dao 层接口的代理类

  1. 实现接口的任意方法都会调用代理类里面的 invoke()方法
  2. 需要获取 statementId 才能调用方法,需要将其定义为接口全限定名.方法名。
  3. 根据返回类型来执行不同的方法
@Override
    public  T getMapper(Class mapperClass) {
        // 使用JDK动态代理来为Dao接口生成代理对象,并返回

        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
                // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
                // 获取方法名:findAll
                String methodName = method.getName();
                //获取接口全限定名
                String className = method.getDeclaringClass().getName();
                //拼接ststementid
                String statementId = className+"."+methodName;
                // 准备参数2:params:args
                // 获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                // 判断是否进行了 泛型类型参数化
                if(genericReturnType instanceof ParameterizedType){
                    List objects = selectList(statementId, args);
                    return objects;
                }

                return selectOne(statementId,args);

            }
        });

        return (T) proxyInstance;
    }
 
第二部分:Mybatis 相关概念 
2.1 对象/关系数据库映射(ORM) 

ORM 全称 Object/Relation Mapping:表示对象-关系映射的缩写

ORM 完成面向对象的编程语言到关系数据库的映射。当 ORM 框架完成映射后,程序员既可以利用面向 对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。ORM 把关系数据库包装成面向对 象的模型。ORM 框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。

采用 ORM 框架 后,应用程序不再直接访问底层数据库,而是以面向对象的放松来操作持久化对象,而 ORM 框架则将这 些面向对象的操作转换成底层 SQL 操作。

ORM 框架实现的效果:把对持久化对象的保存、修改、删除 等 操作,转换为对数据库的操作

2.2 Mybatis 简介

MyBatis 是一款优秀的基于 ORM 的半自动轻量级持久层框架,它支持定制化 SQL、存储过程以及高级映 射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。

MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO (Plain Old Java Objects,普通老式 Java 对 象) 为数据库中的记录。

2.3 Mybatis 历史

原是 apache 的一个开源项目 iBatis, 2010 年 6 月这个项目由 apache software foundation 迁移到了 google code,随着开发团队转投 Google Code 旗下,ibatis3.x 正式更名为 Mybatis ,代码于 2013 年 11 月迁移到 Github。

iBATIS 一词来源于“internet”和“abatis”的组合,是一个基于 Java 的持久层框架。iBATIS 提供的持久层框 架包括 SQL Maps 和 Data Access Objects(DAO)

2.4 Mybatis 优势

Mybatis 是一个半自动化的持久层框架,对开发人员开说,核心 sql 还是需要自己进行优化,sql 和 java 编 码进行分离,功能边界清晰,一个专注业务,一个专注数据。

第三部分:Mybatis 基本应用 3.1 快速入门

MyBatis 官网地址:http://www.mybatis.org/mybatis-3/

3.1.1 开发步骤:

开发步骤

① 添加 MyBatis 的坐标

② 创建 user 数据表

③ 编写 User 实体类

④ 编写映射文件 UserMapper.xml

⑤ 编写核心文件 SqlMapConfig.xml

⑥ 编写测试类

代码结构

3.1.2 环境搭建:
    
        UTF-8
        UTF-8
        1.8
        1.8
        1.8
    

    
    
        
        
            org.mybatis
            mybatis
            3.4.5
        
        
        
            mysql
            mysql-connector-java
            5.1.6
            runtime
        
        
        
            junit
            junit
            4.12
        
    

创建 user 数据表

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(50) DEFAULT NULL,
  `password` varchar(50) DEFAULT NULL,
  `birthday` varchar(50) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'lucy', '123', '2019-12-12');
INSERT INTO `user` VALUES ('2', 'tom','123', '2019-12-12');

DROP TABLE IF EXISTS `orders`;
CREATE TABLE `orders` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `ordertime` varchar(255) DEFAULT NULL,
  `total` double DEFAULT NULL,
  `uid` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `uid` (`uid`),
  CONSTRAINT `orders_ibfk_1` FOREIGN KEY (`uid`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of orders
-- ----------------------------
INSERT INTO `orders` VALUES ('1', '2019-12-12', '3000', '1');
INSERT INTO `orders` VALUES ('2', '2019-12-12', '4000', '1');
INSERT INTO `orders` VALUES ('3', '2019-12-12', '5000', '2');

-- ----------------------------
-- Table structure for sys_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_role`;
CREATE TABLE `sys_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `rolename` varchar(255) DEFAULT NULL,
  `roleDesc` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role
-- ----------------------------
INSERT INTO `sys_role` VALUES ('1', 'CTO', 'CTO');
INSERT INTO `sys_role` VALUES ('2', 'CEO', 'CEO');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `userid` int(11) NOT NULL,
  `roleid` int(11) NOT NULL,
  PRIMARY KEY (`userid`,`roleid`),
  KEY `roleid` (`roleid`),
  CONSTRAINT `sys_user_role_ibfk_1` FOREIGN KEY (`userid`) REFERENCES `user` (`id`),
  CONSTRAINT `sys_user_role_ibfk_2` FOREIGN KEY (`roleid`) REFERENCES `sys_role` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '1');
INSERT INTO `sys_user_role` VALUES ('2', '1');
INSERT INTO `sys_user_role` VALUES ('1', '2');
INSERT INTO `sys_user_role` VALUES ('2', '2');


编写 User 实体


public class User {

    private Integer id;
    private String username;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                '}';
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }
}

编写 UserMapper 映射文件





    

    
    
         select * from user
    


    
    
       
    

    
    
    
        insert into user values(#{id},#{username})
    

    
    
        update user set username = #{username} where id = #{id}
    

    
    
        delete from user where id = #{abc}
    

    
    

    
    


编写 MyBatis 核心文件 sqlMapConfig.xml






    
    

    
    
        
      
        
        
    

    
    
        
            
            
            
            
                
                
                
                
            
        
    

    
    
        
    


JDBC 数据信息

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///zdy_mybatis?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=root

Dao mapper 代理

接口名称和方法名称对应于 sql mapper 里面的 namespace 和 sql id

public interface IUserDao {

    //查询所有用户
    public List findAll() throws IOException;
    //多条件组合查询:演示if
    public List findByCondition(User user);
    //多值查询:演示foreach
    public List findByIds(int[] ids);

}

编写测试代码

public class MybatisTest {

    @Test
    public void test1() throws IOException {
        //1.Resources工具类,配置文件的加载,把配置文件加载成字节输入流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //2.解析了配置文件,并创建了sqlSessionFactory工厂
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //3.生产sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();// 默认开启一个事务,但是该事务不会自动提交
                                                                //在进行增删改操作时,要手动提交事务
        //4.sqlSession调用方法:查询所有selectList  查询单个:selectOne 添加:insert  修改:update 删除:delete
        List users = sqlSession.selectList("user.findAll");
        for (User user : users) {
            System.out.println(user);
        }
        sqlSession.close();

    }

    @Test
    public void test2() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);//事务自动提交

        User user = new User();
        user.setId(6);
        user.setUsername("tom");
        sqlSession.insert("user.saveUser",user);


        sqlSession.close();
    }

    @Test
    public void test3() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user = new User();
        user.setId(4);
        user.setUsername("lucy");
        sqlSession.update("user.updateUser",user);
        sqlSession.commit();

        sqlSession.close();
    }

    @Test
    public void test4() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();


        sqlSession.delete("com.lagou.dao.IUserDao.deleteUser",6);
        sqlSession.commit();

        sqlSession.close();
    }

    @Test
    public void test5() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        List all = mapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }

    @Test
    public void test6() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

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

        User user1 = new User();
        user1.setId(4);
        user1.setUsername("lucy");

        List all = mapper.findByCondition(user1);
        for (User user : all) {
            System.out.println(user);
        }
    }

    @Test
    public void test7() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();

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

        int[] arr = {1, 2};

        List all = mapper.findByIds(arr);
        for (User user : all) {
            System.out.println(user);
        }

    }
}

3.2 核心配置文件分析 3.2,1 MyBatis 的映射文件概述

数据库环境的配置,支持多环境配置

其中,事务管理器(transactionManager)类型有两种:

JDBC:这个配置就是直接使用了 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作 用域。

MANAGED:这个配置几乎没做什么。它从来不提交或回滚一个连接,而是让容器来管理事务的整个生 命周期(比如 JEE 应用服务器的上下文)。 默认情况下它会关闭连接,然而一些容器并不希望这样,因 此需要将 closeConnection 属性设置为 false 来阻止它默认的关闭行为。 其中,数据源(dataSource)类型有三种:

UNPOOLED:这个数据源的实现只是每次被请求时打开和关闭连接。

POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来。

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

mapper 标签

 使用相对于类路径的资源引用,例如:

•使用完全限定资源定位符(URL),例如:

•使用映射器接口实现类的完全限定类名,例如:

•将包内的映射器接口实现全部注册为映射器,必须同包同名例如:

typeAliases 标签

上面我们是自定义的别名,mybatis 框架已经为我们设置好的一些常用的类型的别名

SqlSession 工厂构建器 SqlSessionFactoryBuilder

常用 API:SqlSessionFactory build(InputStream inputStream)

通过加载 mybatis 的核心文件的输入流的形式构建一个 SqlSessionFactory 对象

String resource = "org/mybatis/builder/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource); 
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); 
SqlSessionFactory factory = builder.build(inputStream);

SqlSession 工厂对象 SqlSessionFactory

SqlSessionFactory 有多个个方法创建 SqlSession 实例。常用的有如下两个

SqlSession 会话对象 SqlSession 实例在 MyBatis 中是非常强大的一个类。

在这里你会看到所有执行语句、提交或回滚事务和 获取映射器实例的方法。

执行语句的方法主要有:

 T selectOne(String statement, Object parameter) 
 List selectList(String statement, Object parameter) 
int insert(String statement, Object parameter) 
int update(String statement, Object parameter) 
int delete(String statement, Object parameter)
void commit() 
void rollback()
3.2.2 代理开发方式

代理开发方式介绍

采用 Mybatis 的代理开发方式实现 DAO 层的开发,这种方式是我们后面进入企业的主流。

Mapper 接口开发方法只需要程序员编写 Mapper 接口(相当于 Dao 接口),由 Mybatis 框架根据接口 定义创建接口的动态代理对象,代理对象的方法体同上边 Dao 接口实现类方法。

Mapper 接口开发需要遵循以下规范:

  1. Mapper.xml 文件中的 namespace 与 mapper 接口的全限定名相同
  2. Mapper 接口方法名和 Mapper.xml 中定义的每个 statement 的 id 相同
  3. Mapper 接口方法的输入参数类型和 mapper.xml 中定义的每个 sql 的 parameterType 的类型相同
  4. Mapper 接口方法的输出参数类型和 mapper.xml 中定义的每个 sql 的 resultType 的类型相同

测试代理方式

  @Test
        public void test5() throws IOException {

//            IUserDao userDao =  new UserDaoImpl();
//            List users = userDao.findAll();

            InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            IUserDao mapper = sqlSession.getMapper(IUserDao.class);

            List users = mapper.findAll();
            for (User user : users) {
                System.out.println(user);
            }
        }
3.2.3 动态 sql 语句

where 和 if

where 会根据情况去掉第一个 and

 
        select  * from user
        
            
                #{id}
            
        
    
@Test
    public void test7() throws IOException {

        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        IUserDao mapper = sqlSession.getMapper(IUserDao.class);
        int ids[] ={1,2};
        List users = mapper.findByIds(ids );
        for (User user : users) {
            System.out.println(user);
        }
    }

foreach 标签的属性含义如下:

用于遍历集合,它的属性:

collection:代表要遍历的集合元素,注意编写时不要写#{}

open:代表语句的开始部分

close:代表结束部分

item:代表遍历集合的每个元素,生成的变量名

sperator:代表分隔符

3.2.4 延迟加载

延迟加载

就是在需要用到数据时才进行加载,不需要用到数据时就不加载数据。延迟加载也称懒加载。

局部延迟加载

在 association 和 collection 标签中都有一个 fetchType 属性,通过修改它的值,可以修改局部的加载策 略。




全局延迟加载

在 Mybatis 的核心配置文件中可以使用 setting 标签修改全局的加载策略。

  
 

延迟加载原理实现

它的原理是,使用 CGLIB 或 Javassist( 默认 ) 创建目标对象的代理对象。当调用代理对象的延迟加载属 性的 getting 方法时,进入拦截器方法。比如调用 a.getB().getName() 方法,进入拦截器的 invoke(…) 方法,发现 a.getB() 需要延迟加载时,那么就会单独发送事先保存好的查询关联 B 对 象的 SQL ,把 B 查询上来,然后调用 a.setB(b) 方法,于是 a 对象 b 属性就有值了,接着完成 a.getB().getName() 方法的调用。这就是延迟加载的基本原理

总结:延迟加载主要是通过动态代理的形式实现,通过代理拦截到指定方法,执行数据加载。

第四部分:Mybatis 复杂映射开发

建表信息、依赖、数据库核心配置同上

1.1 一对一查询

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户 一对一查询的需求:查询一个订单,与此同时查询出该订单所属的用户

对应的 sql 语句:select * from orders o,user u where o.uid=u.id;

创建 Order 和 User 实体

public class Order {

    private Integer id;
    private String orderTime;
    private Double total;

//代表当前订单从属于哪一个客户 
private User user;

}

public class User {
 private Integer id;
 private String username;

}

创建 OrderMapper 接口

public interface OrderMapper {
List findOrderAndUser(); 
}

配置 OrderMapper.xml






    

    
        
        
        

        
            
            
        
    

    
    
        select * from orders o,user u where o.uid = u.id
    


sqlMapConfig.xml 添加对应到 mapper

添加引入映射配置文件路径相对于 sqlMapConfig.xml






    
    

    
    
        
      
        
        
    

    
    
        
            
            
            
            
                
                
                
                
            
        
    

    
    
        
	
    

测试

InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession sqlSession = build.openSession();
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class); 
List all = mapper.findOrderAndUser(); 
for(Order order : all){ System.out.println(order); }
1.2 一对多查询

用户表和订单表的关系为,一个用户有多个订单,一个订单只从属于一个用户

一对多查询的需求:查询一个用户,与此同时查询出该用户具有的订单

对应的 sql 语句:select *,o.id oid from user u left join orders o on u.id=o.uid;

查询的结果如下:

MyBatis 核心文件 sqlMapConfig.xml 改为包扫描

注意在 resources 目录下建立与接口同包同名的 mapper.xml






    
    

    
    
        
      
        
        
    

    
    
        
            
            
            
            
                
                
                
                
            
        
    
   
    
    
       
        
    

修改 User 实体 添加如下

//代表当前用户具备哪些订单 
private List orderList;

创建 UserMapper 接口

public interface UserMapper { 
List findAll(); 
}

配置 UserMapper.xml

   
        
        
        
            
            
            
        
    

    
        select * from user u left join orders o on u.id = o.uid
    

Test

    private IUserMapper userMapper;
    private IOrderMapper orderMapper;

    @Before
    public void befor() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        userMapper = sqlSession.getMapper(IUserMapper.class);
        orderMapper = sqlSession.getMapper(IOrderMapper.class);
    }
@Test
    public void oneToMany(){
        List all = userMapper.findAll();
        for (User user : all) {
            System.out.println(user);
        }

    }
1.3 多对多查询

用户表和角色表的关系为,一个用户有多个角色,一个角色被多个用户使用

多对多查询的需求:查询用户同时查询出该用户的所有角色

-- 对应的sql语句:
select u.,r.,r.id rid from user u 
left join user_role ur on u.id=ur.user_id inner join role r on ur.role_id=r.id;

创建 Role 实体,修改 User 实体

public class User implements Serializable {

    private Integer id;

    private String username;

    private List orderList;

    private List roleList;
}
public class Role {

    private Integer id;
    private String roleName;
    private String roleDesc;
}

添加 UserMapper 接口方法

List findAllUserAndRole();

修改 UserMapper.xml






    

    
        
        
        
            
            
            
        
    

    
        select * from user u left join orders o on u.id = o.uid
    
    
        
        
        
            
            
            
        
    

    
select * from user where id=#{id} 

设置 statement 配置中的 flushCache="true”属性,默认情况下为 true,即刷新缓存,如果改成 false 则 不 会刷新。使用缓存时如果手动修改数据库表中的查询数据会出现脏读。


 select * from user 

对于注解则是添加注解

@CacheNamespace(implementation = RedisCache.class)//开启二级缓存
public interface IUserMapper {}

redis.properties

下面的内容和默认的一致可以删除,如果要修改则必须添加且名称为 redis.properties 不能修改

redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0

全局配置文件 sqlMapConfig.xml 文件中加入如下代码

 



最后需要 pojo 实现 Serializable 进行缓存序列化

public class User implements Serializable {}

代码测试

  @Test
    public void SecondLevelCache(){
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        SqlSession sqlSession3 = sqlSessionFactory.openSession();

        IUserMapper mapper1 = sqlSession1.getMapper(IUserMapper.class);
        IUserMapper mapper2 = sqlSession2.getMapper(IUserMapper.class);
        IUserMapper mapper3 = sqlSession3.getMapper(IUserMapper.class);

        User user1 = mapper1.findUserById(1);
        sqlSession1.close(); //清空一级缓存
        User user = new User();
        user.setId(1);
        user.setUsername("lisi");
        mapper3.updateUser(user);
        sqlSession3.commit();
        User user2 = mapper2.findUserById(1);
        System.out.println(user1==user2);

    }

源码分析

RedisConfig 类

public class RedisConfig extends JedisPoolConfig {
    private String host = "localhost";
    private int port = 6379;
    private int connectionTimeout = 2000;
    private int soTimeout = 2000;
    private String password;
    private int database = 0;
    private String clientName;

    public RedisConfig() {
    }

RedisCache 类

public final class RedisCache implements Cache {
  public void putObject(final Object key, final Object value) {
        this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                jedis.hset(RedisCache.this.id.toString().getBytes(), key.toString().getBytes(), SerializeUtil.serialize(value));
                return null;
            }
        });
    }

    public Object getObject(final Object key) {
        return this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                return SerializeUtil.unserialize(jedis.hget(RedisCache.this.id.toString().getBytes(), key.toString().getBytes()));
            }
        });
    }

    public Object removeObject(final Object key) {
        return this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                return jedis.hdel(RedisCache.this.id.toString(), new String[]{key.toString()});
            }
        });
    }

    public void clear() {
        this.execute(new RedisCallback() {
            public Object doWithRedis(Jedis jedis) {
                jedis.del(RedisCache.this.id.toString());
                return null;
            }
        });
    }

mybatis-redis 在存储数据的时候,是使用的 hash 结构,把 cache 的 id 作为这个 hash 的 key (cache 的 id 在 mybatis 中就是 mapper 的 namespace);这个 mapper 中的查询缓存数据作为 hash 的 field,需要缓存的内容直接使用 SerializeUtil 存储,SerializeUtil 和其他的序列化类差不多,负责 对象的 序列化和反序列化;

第七部分:Mybatis 插件 1.1 插件简介

一般情况下,开源框架都会提供插件或其他形式的拓展点,供开发者自行拓展。这样的好处是显而易见 的,一是增加了框架的灵活性。二是开发者可以结合实际需求,对框架进行拓展,使其能够更好的工 作。以 MyBatis 为例,我们可基于 MyBati s 插件机制实现分页、分表,监控等功能。由于插件和业务 无 关,业务也无法感知插件的存在。因此可以无感植入插件,在无形中增强功能

1.2 Mybatis 插件介绍

Mybati s 作为一个应用广泛的优秀的 ORM 开源框架,这个框架具有强大的灵活性,在四大组件

(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插 件扩 展机制。Mybatis 对持久层的操作就是借助于四大核心对象。

MyBatis 支持用插件对四大核心对象进 行拦 截,对 mybatis 来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis 中的四大对象都是代理对象

MyBatis 所允许拦截的方法如下

执行器 Executor (update、query、commit、rollback 等方法);

SQL 语法构建器 StatementHandler (prepare、parameterize、batch、updates query 等方 法);

参数处理器 ParameterHandler (getParameterObject、setParameters 方法);

结果集处理器 ResultSetHandler (handleResultSets、handleOutputParameters 等方法);

1.3 Mybatis 插件原理

在四大对象创建的时候

1、每个创建出来的对象不是直接返回的,而是 interceptorChain.pluginAll(parameterHandler);

2、获取到所有的 Interceptor (拦截器)(插件需要实现的接口);调用 interceptor.plugin(target);返 回 target 包装后的对象

3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP (面向切面)我们的插件可 以 为四大对象创建出代理对象,代理对象就可以拦截到四大对象的每一个执行;

插件具体是如何拦截

interceptorChain 保存了所有的拦截器(interceptors),是 mybatis 初始化的时候创建的。调用拦截器链 中的拦截器依次的对目标进行拦截或增强。interceptor.plugin(target)中的 target 就可以理解为 mybatis 中的四大对象。返回的 target 是被重重代理后的对象

拦截 Executor 的 query 方法,那么可以这样定义插件

定义拦截器指定拦截内容

@Intercepts({ 
@Signature( 
type = Executor.class, 
method = "query", 
args= {MappedStatement.class,Object.class,RowBounds.class,ResultHandler.class} ) }) 
public class ExeunplePlugin implements Interceptor { //省略逻辑 }

将插件配置到 sqlMapConfig.xm l 中。


 

这样 MyBatis 在启动时可以加载插件,并保存插件实例到相关对象(InterceptorChain,拦截器链) 中。待 准备工作做完后,MyBatis 处于就绪状态。

我们在执行 SQL 时,需要先通过 DefaultSqlSessionFactory 创 建 SqlSession。Executor 实例会在创建 SqlSession 的过程中被创建, Executor 实例创建完毕后, MyBatis 会通过 JDK 动态代理为实例生成代理类。这样,插件逻辑即可在 Executor 相关方法被调用前执 行。

1.4 自定义插件

步骤

Mybatis 插件接口-Interceptor

intercept 方法,插件的核心方法

plugin 方法,生成 target 的代理对象

setProperties 方法,传递插件所需参数

实现自定义插件

添加拦截器 MyPlugin 实现 Interceptor 方法

@Intercepts({
        @Signature(type= StatementHandler.class,
                  method = "prepare",
                  args = {Connection.class,Integer.class})
})
public class MyPlugin implements Interceptor {

    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        System.out.println("对方法进行了增强....");
        return invocation.proceed(); //原方法执行
    }

    
    @Override
    public Object plugin(Object target) {
        Object wrap = Plugin.wrap(target, this);
        return wrap;
    }

    
    @Override
    public void setProperties(Properties properties) {
        System.out.println("获取到的配置文件的参数是:"+properties);
    }
}

sqlMapConfig.xml 配置插件信息

  
  	
            
         
  

mapper 接口

public interface UserMapper { List selectUser(); }

mapper.xml


  

测试类

    private UserMapper userMapper;
    private SqlSession sqlSession;
    
    @Before
    public void beforTest() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = build.openSession();
        userMapper = sqlSession.getMapper(IUserMapper.class);
   @Test
    public void selectUser(){
        List users = userMapper.selectUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
1.5 pageHelper 分页插件

MyBati s 可以使用第三方的插件来对功能进行扩展,分页助手 PageHelper 是将分页的复杂操作进行封 装,使用简单的方式即可获得分页的相关数据

开发步骤

① 导入通用 PageHelper 的坐标

② 在 mybatis 核心配置文件中配置 PageHelper 插件

③ 测试分页数据获取

具体实现

① 导入通用 PageHelper 坐标

      
        
            com.github.pagehelper
            pagehelper
            3.7.5
        
        
            com.github.jsqlparser
            jsqlparser
            0.9.1
        

② 在 mybatis 核心配置文件中配置 PageHelper 插件

声明插件,指定方言是 mysql 而不是 oracle

    
        
            
        
      
    

③ 测试分页代码实现

只需要添加 PageHelper.startPage(1,1); 指定开始页和每页显示数据

 @Test
    public void pageHelperTest() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        sqlSession = build.openSession();
	PageHelper.startPage(1,1);
        mapper = sqlSession.getMapper(IUserMapper.class);
        List alls = mapper.findAlls();
        for (User user : alls
        ) {
            System.out.println(user);
            }
        PageInfo pageInfo = new PageInfo<>(alls);
        System.out.println("总条数:"+pageInfo.getTotal());
        System.out.println("总页数:"+pageInfo.getPages());
        System.out.println("当前页数:"+pageInfo.getPageNum());
        System.out.println("每页显示条数:"+pageInfo.getPageSize());

    }

测试

1.6 通用 mapper

什么是通用 Mapper

通用 Mapper 就是为了解决单表增删改查,基于 Mybatis 的插件机制。开发人员不需要编写 SQL,不需要 在 DAO 中增加方法,只要写好实体类,就能支持相应的增删改查方法

一个基本表的增删改查方法的集成,基于插件机制,只需要继承该接口即可不需要写 SQL 语句

通用 Mapper 的实现

在 pom.xml 中引入 mapper 的依赖

	 
            tk.mybatis
            mapper
            3.1.2
        

Mybatis 配置文件中完成配置 插件

   
        
            
        
        
            
        
    

实体类设置 表主键

如果不对应则需要添加 @Column

@Table(name = "user")
public class User implements Serializable {

    @Id //对应的是注解id
    @GeneratedValue(strategy = GenerationType.IDENTITY) //设置主键的生成策略
    private Integer id;

    private String username;

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                '}';
    }

定义通用 mapper

import tk.mybatis.mapper.common.Mapper;

public interface UserMapper extends Mapper {

}

测试

 @Test
    public void mapperTest() throws IOException {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = new User();
        user.setId(1);
        User user1 = mapper.selectOne(user);
        System.out.println(user1);


        //2.example方法
        Example example = new Example(User.class);
	//条件查询
        example.createCriteria().andEqualTo("id",1);
        List users = mapper.selectByExample(example);
        for (User user2 : users) {
            System.out.println(user2);
        }

mapper 基础接口

第八部分:Mybatis 架构原理 1.1 架构设计

我们把 Mybatis 的功能架构分为三层:

(1) API 接口层:提供给外部使用的接口 API,开发人员通过这些本地 API 来操纵数据库。接口层一接收到 调用请求就会调用数据处理层来完成具体的数据处理。

MyBatis 和数据库的交互有两种方式:

a. 使用传统的 MyBatis 提供的 API ;

b. 使用 Mapper 代理的方式

(2) 数据处理层:负责具体的 SQL 查找、SQL 解析、SQL 执行和执行结果映射处理等。它主要的目的是根 据调用的请求完成一次数据库操作。

(3) 基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是 共 用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑

1.2 主要构件及其相互关系
构件描述
SqlSession作为 MyBatis 工作的主要顶层 API,表示和数据库交互的会话,完成必要数 据库增删改查功能
ExecutorMyBatis 执行器,是 MyBatis 调度的核心,负责 SQL 语句的生成和查询缓 存 的维护
StatementHandler封装了 JDBC Statement 操作,负责对 JDBC statement 的操作,如设置参 数、将 Statement 结果集转换成 List 集合。
ParameterHandler负责对用户传递的参数转换成 JDBC Statement 所需要的参数,
ResultSetHandler负责将 JDBC 返回的 ResultSet 结果集对象转换成 List 类型的集合;
TypeHandler负责 java 数据类型和 jdbc 数据类型之间的映射和转换
MappedStatementMappedStatement 维护了一条 <select | update | delete | insert> 节点 的封 装
SqlSource负责根据用户传递的 parameterObject,动态地生成 SQL 语句,将信息封 装到 BoundSql 对象中,并返回
BoundSql表示动态生成的 SQL 语句以及相应的参数信息

1.3 总体流程

(1) 加载配置并初始化

触发条件:加载配置文件 配置来源于两个地方,一个是配置文件(主配置文件 conf.xml,mapper 文件*.xml),—个是 java 代码中的 注 解,将主配置文件内容解析封装到 Configuration,将 sql 的配置信息加载成为一个 mappedstatement 对 象,存储在内存之中

(2) 接收调用请求

触发条件:调用 Mybatis 提供的 API

传入参数:为 SQL 的 ID 和传入参数对象

处理过程:将请求传递给下层的请求处理层进行处理。

(3) 处理操作请求

触发条件:API 接口层传递请求过来

传入参数:为 SQL 的 ID 和传入参数对象

处理过程:

(A) 根据 SQL 的 ID 查找对应的 MappedStatement 对象。

(B) 根据传入参数对象解析 MappedStatement 对象,得到最终要执行的 SQL 和执行传入参数。

© 获取数据库连接,根据得到的最终 SQL 语句和执行传入参数到数据库执行,并得到执行结果。

(D) 根据 MappedStatement 对象中的结果映射配置对得到的执行结果进行转换处理,并得到最终的处理 结果。

(E) 释放连接资源。

(4) 返回处理结果

将最终的处理结果返回。

1.4 重点介绍

本质是对 JDBC 的封装

执行步骤

根据配置文件 sqlMapConfig.xml 获取配置文件信息流

根据流解析 XML 获取 Configuration 对象封装了配置信息及 mapper 信息 List>

继续封装成 DefaultSqlSession 并用 SqlSession 接口进行接收

通过 SqlSession 的 API 根据具体的 namespace.id 获取到具体的 SQL 进行执行

SqlSession

SqlSession 是一个接口,它有两个实现类:DefaultSqlSession (默认)和 SqlSessionManager (弃用,不做介绍)

SqlSession 是 MyBatis 中用于和数据库交互的顶层类,通常将它与 ThreadLocal 绑定,一个会话使用一 个 SqlSession,并且在使用完毕后需要 close

SqlSession 中的两个最重要的参数,configuration 与初始化时的相同,Executor 为执行器

public class DefaultSqlSession implements SqlSession { 
private final Configuration configuration; 
private final Executor executor; j

Executor

Executor 也是一个接口,他有三个常用的实现类:

BatchExecutor (重用语句并执行批量更新)

ReuseExecutor (重用预处理语句 prepared statements)

SimpleExecutor (普通的执行器,默认)

//此方法在SimpleExecutor的父类baseExecutor中实现 
public  List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
 //根据传入的参数动态获得SQL语句,最后返回用BoundSql对象表示 
BoundSql boundSql = ms.getBoundSql(parameter); 
//为本次查询创建缓存的Key 
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); 
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);

}
总结

完整代码参考 https://github.com/LaneDu/lane-mybatis-11,感觉懂了,又感觉不怎么懂,或许是因为一般使用的时候都是结合 spring 系列吧!

强烈建议接着看看 MyBatis Plus 笔记 我学完MyBatis Plus 感觉对MyBatis、Sprin、Spring Boot的理解加深了很多

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号