- 一、MyBatis理论基础
- 1.1 MyBatis简介
- 1.2 ORM
- 1.3 为什么说Mybatis是半自动ORM映射工具
- 1.4 传统JDBC开发存在的问题
- 1.5 JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的
- 1.6 Mybatis的优缺点
- 1.7 Hibernate和MyBatis的比较
- 1.8 存储过程的优缺点
- 1.9 为什么需要预编译
- 1.10 MyBatis编程步骤
- 1.11 MyBatis的工作原理
- 1.12 MyBatis的功能架构
- 1.13 Executor执行器
- 1.14 Mybatis是否支持延迟加载
- 1.15 一级缓存什么时候会失效
- 1.16 二级缓存怎么开启
- 1.17 一集缓存和二级缓存的查询顺序
- 1.18 如何获取自动生成的主键值
- 1.19 Mybatis框架源码的大致流程
- 二、Mybatis的使用
- 2.1 Mybatis的基础使用
- 2.1.1 示例
- 2.1.2 mybatis-config.xml中的标签顺序
- 2.2 #{}和${}
- 2.3 XXXMapper.xml的使用
- 2.4 MyBatis接口绑定的实现方式
- 2.5 模糊查询like语句该怎么写
- 2.6 当实体类中的属性名和表中的字段名不一样时的处理方法
- 2.7 使用MyBatis的mapper接口调用时的4个要求
- 2.8 Mybatis中不同的Xml映射文件,id是否可以重复
- 2.9 代理主键和自然主键
- 2.10 获取主键
- 2.10.1 使用useGeneratedKeys和keyProperty属性
- 2.10.2 使用< selectKey >子标签
- 2.11 批量查询
- 2.12 动态sql
- 2.13 Mybatis动态sql是做什么的
MyBatis 是一款优秀的持久层框架,一个半 ORM(对象关系映射)框架,它支持定制化SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
MyBatis的核心配置文件是mybatis-config.xml。
- 1、定制化SQL
同为持久层框架的Hibernate,对操作数据库的支持方式较多,完全面向对象的、原生SQL的和HQL的方式。MyBatis只支持原生的SQL语句,这个“定制化”是相对Hibernate完全面向对象的操作方式的。 - 2、存储过程
储存过程是实现某个特定功能的一组sql语句集,是经过编译后存储在数据库中。当出现大量的事务回滚或经常出现某条语句时,使用存储过程的效率往往比批量操作要高得多。
MyBatis是支持存储过程的,可以看个小例子。假设有一张表student:
create table student ( id bigint not null, name varchar(30), sex char(1), primary key (id) );
有一个添加记录的存储过程:
create procedure pro_addStudent (IN id bigint, IN name varchar(30), IN sex char(1)) begin insert into student values (id, name, sex); end
此时就可以在mapper.xml文件中调用存储过程:
- 3、高级映射
可以简单理解为支持关联查询。 - 4、避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。使用Mybatis时,数据库的连接配置信息,是在mybatis-config.xml文件中配置的,至于获取查询结果的代码,也是尽量做到了代码的简洁,以模糊查询为例,需要做两步工作:
1>首先在配置文件中写上SQL语句,示例:
2>在Java代码中调用此语句,示例:
Listcs = session.selectList("listCategoryByName","cat"); for (Category c : cs) { System.out.println(c.getName()); }
第三句话可以这样理解:
- 5、Mybatis中ORM的映射方式也是比较简单的
先看段示例代码:
"resultType"参数的值指定了SQL语句返回对象的类型。
- 适用场景
MyBatis专注于SQL本身,是一个足够灵活的DAO层解决方案。
MyBatis框架的适用场景:对性能的要求很高,或者需求变化较多的项目,如互联网项目,MyBatis将是不错的选择。
了解ORM,先了解下面两个概念:
- 持久化,即把数据(如内存中的对象)保存到可永久保存的存储设备中。持久化的主要应用是将内存中的数据存储在关系型的数据库中,当然也可以存储在磁盘文件中、XML数据文件中等等。
- 持久层,即专注于实现数据持久化应用领域的某个特定系统的一个逻辑层面,将数据使用者和数据实体相关联。
ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和复杂的SQL语句打交道,只需简单的操作对象的属性和方法。
ORM的理解:
- 它是一种将内存中的对象保存到关系型数据库中的技术;
- 主要负责实体对象的持久化,封装数据库访问细节;
- 提供了实现持久化层的另一种模式,采用映射元数据(XML)来描述对象-关系的映射细节,使得ORM中间件能在任何一个Java应用的业务逻辑层和数据库之间充当桥梁。
Java典型的ORM框架:
1)hibernate:全自动的框架,强大、复杂、笨重、学习成本较高;
2)Mybatis:半自动的框架, 必须要自己写sql;
3)JPA:JPA全称Java Persistence API、JPA通过JDK 5.0注解或XML描述对象-关系表的映射关系,是Java自带的框架。
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
JDBC(Java Data base Connection,java数据库连接)是一种用于执行SQL语句的Java API,可以为多种关系数据库提供统一访问,它由一组用Java语言编写的类和接口组成.JDBC提供了一种基准,据此可以构建更高级的工具和接口,使数据库开发人员能够编写数据库应用程序。
传统的JDBC开发指的通常是如下的流程:加载驱动 -> 建立连接 -> 定义sql语句 -> 准备静态处理块对象 -> 执行sql语句 -> 处理结果集 -> 关闭连接。
传统的JDBC开发的最原始的开发方式,有以下问题:
- 1、频繁创建数据库连接对象、释放,容易造成系统资源浪费,影响系统性能。可以使用连接池解决这个问题,但是使用jdbc需要自己实现连接池。
- 2、sql语句定义、参数设置、结果集处理存在硬编码。实际项目中sql语句变化的可能性较大,一旦发生变化,需要修改java代码,系统需要重新编译,重新发布,不好维护。
- 3、使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
- 4、结果集处理存在重复代码,处理麻烦。 这点也容易理解,在使用JDBC时,还需要用 ResultSet之类的方式来遍历数据库中查询出来的一条条字段,这肯定是不方便的,示例:
ResultSet rs = s.executeQuery(sql);
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString(2);
float hp = rs.getFloat("hp");
int damage = rs.getInt(4);
System.out.printf("%dt%st%ft%d%n", id, name, hp, damage);
}
1.5 JDBC编程有哪些不足之处,MyBatis是如何解决这些问题的
- JDBC的问题1、数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能。
MyBatis解决的解决方法:在mybatis-config.xml中配置数据连接池,使用连接池管理数据库连接。 - JDBC的问题2、Sql语句写在代码中造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变Java代码。
MyBatis解决的解决方法:将Sql语句配置在XXXXmapper.xml文件中,与Java代码分离。 - JDBC的问题3、向sql语句传参数麻烦,因为sql语句的where条件不一定,可能多也可能少,占位符需要和参数一一对应。
MyBatis解决的解决方法: Mybatis自动将Java对象映射至sql语句。 - JDBC的问题4、对结果集解析麻烦,sql变化导致解析代码变化,且解析前需要遍历,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至Java对象。 示例:
Category c= session.selectOne("getCategory",2);
1.6 Mybatis的优缺点
与传统的数据库访问技术相比,Mybatis有以下优点:
- 1、基于SQL语句编程,相当灵活,不会对应用程序或者数据库的现有设计造成任何影响,SQL写在XML里,解除sql与程序代码的耦合,便于统一管理;提供XML标签,支持编写动态SQL语句,并可重用。
- 2、与JDBC相比,减少了50%以上的代码量,消除了JDBC大量冗余的代码,不需要手动开关连接。
- 3、很好的与各种数据库兼容。
- 4、提供映射标签,支持对象与数据库的ORM字段关系映射;提供对象关系映射标签,支持对象关系组件维护。
- 5、能够与Spring很好的集成。
Mybatis的缺点:
- 1、SQL语句的编写工作量较大,尤其当字段多、关联表多时,对开发人员编写SQL语句的功底有一定要求
- 2、SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库(可以在 mybatis-config.xml配置databaseIdProvider来弥补),示例:
然后在xml文件中,就可以针对不同的数据库,写不同的sql语句。
1.7 Hibernate和MyBatis的比较 Hibernate是一个开放源代码的对象关系映射框架,它对JDBC进行了非常轻量级的对象封装,建立对象与数据库表的映射。是一个全自动的、完全面向对象的持久层框架。
Mybatis是一个开源对象关系映射框架,原名:ibatis,2010年由谷歌接管以后更名。是一个半自动化的持久层框架。
Hibernate和MyBatis的区别:
- 1、开发速度
hibernate,sql语句已经被封装(可以采用完全面向对象的方式开发),直接可以使用,加快系统开发;
Mybatis 属于半自动化,sql需要手工完成,稍微繁琐; - 2、sql优化方面
Hibernate自动生成sql,有些语句较为繁琐,会多消耗一些性能;
Mybatis 手动编写sql,可以避免不需要的查询,提高系统性能; - 3、对象管理比对
Hibernate是完整的对象-关系映射的框架,开发工程中,无需过多关注底层实现,只要去管理对象即可。示例:假设已经有个Product类,接下来就可以创建一个Product.hbm.xml,用来表明该类的一个实例就和数据库中的一条数据像对应:
而Mybatis中没有类似的文件来管理类的实例和数据库的数据映射关系。
Mybatis需要自行管理映射关系。
- 4、缓存方面
相同点:Hibernate和Mybatis的二级缓存除了采用系统默认的缓存机制外,都可以使用其他第三方缓存方案,创建适配器来完全覆盖缓存行为。
不同点:Hibernate的二级缓存配置在SessionFactory生成的配置文件中进行详细配置,然后再在具体的表-对象映射中配置是哪种缓存。
MyBatis的二级缓存配置都是在每个具体的表-对象映射中进行详细配置,这样针对不同的表可以自定义不同的缓存机制。并且Mybatis可以在命名空间中共享相同的缓存配置和实例,通过Cache-ref来实现。
Hibernate优势:
- Hibernate的DAO层开发比MyBatis简单,Mybatis需要维护SQL和结果映射。
- Hibernate对对象的维护和缓存要比MyBatis好,对增删改查的对象的维护要方便。
- Hibernate数据库移植性很好,MyBatis的数据库移植性不好,不同的数据库需要写不同SQL。
- Hibernate有更好的二级缓存机制,可以使用第三方缓存。MyBatis本身提供的缓存机制不佳。
Mybatis优势:
- MyBatis可以进行更为细致的SQL优化,可以减少查询字段。
- MyBatis容易掌握,而Hibernate门槛较高。
- sql语句和Java代码耦合性地。
因为Mybatis支持存储过程,因此此处简单提一下存储过程。
- 优点
1)存储过程只在创造时进行编译,以后每次执行存储过程都不需再重新编译,而一般 SQL 语句每执行一次就编译一次,所以使用存储过程可提高数据库执行速度。
2)当对数据库进行复杂操作时(如对多个表进行 增删改查 时),可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用。这些操作,如果用程序来完成,就变成了一条条的 SQL 语句,可能要多次连接数据库。而换成存储,只需要连接一次数据库就可以了。
3)存储过程可以重复使用,可减少数据库开发人员的工作量。
4)安全性高,可设定只有某此用户才具有对指定存储过程的使用权。 - 缺点
1)调试麻烦,但是用 PL/SQL Developer 调试很方便,能够弥补这个缺点。
2)移植问题,数据库端代码当然是与数据库相关的。但是如果是做工程型项目,基本不存在移植问题。
3)重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译(不过也可以设置成运行时刻自动编译)。
4)如果在一个程序系统中大量的使用存储过程,到程序交付后,生产环境中随着用户需求的增加会导致数据结构的变化,此时要修改存储过程来满足变化后的需求,此时的代价将非常巨大,所以存储过程不是越多越好。
预编译定义:SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS(数据库管理系统) 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
- 为什么需要预编译
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。
- 第一种步骤
- 创建SqlSessionFactory;
- 通过SqlSessionFactory创建SqlSession;
- 通过sqlsession执行数据库操作;
- 调用session.commit()提交事务;
- 调用session.close()关闭会话。
示例:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
Category c = new Category();
c.setName("新增加的Category");
session.insert("addCategory",c);
session.commit();
session.close();
- 第二种步骤(更常用)
- 创建 SqlSessionFactory 对象。
- 通过 SqlSessionFactory 获取 SqlSession 对象。
- 通过 SqlSession 获得 Mapper 代理对象。
- 通过 Mapper 代理对象,执行数据库操作。
- 执行成功,则使用 SqlSession 提交事务。
- 执行失败,则使用 SqlSession 回滚事务。
- 最终,关闭 session 会话。
示例:
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
CategoryMapper mapper = session.getMapper(CategoryMapper.class);
List cs = mapper.list();
for (Category c : cs) {
System.out.println(c.getName());
}
session.commit();
session.close();
1.11 MyBatis的工作原理
MyBatis 的工作原理:
- 1、读取 MyBatis 配置文件
mybatis-config.xml 为 MyBatis 的全局配置文件,配置了 MyBatis 的运行环境等信息,例如数据库连接信息。 - 2、加载映射文件:映射文件即 SQL 映射文件(一般是XXXMapper.xml)
该文件中配置了操作数据库的 SQL 语句,需要在 MyBatis 配置文件 mybatis-config.xml 中加载。mybatis-config.xml 文件可以加载多个映射文件,每个文件对应数据库中的一张表。 - 3、构造会话工厂
通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。 - 4、创建会话对象
由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。 - 5、Executor 执行器
MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。 - 6、MappedStatement 对象
在 Executor 接口的执行方法中有一个 MappedStatement 类型的参数,该参数是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
第五步和第六步,在实际的开发过程中,可以“见不到”,因为在执行sqlSessionFactory.openSession( )语句时,如果不传参数,就默认使用SimpleExecutor。
- 7、输入参数映射
输入参数类型可以是 Map、List 等集合类型,也可以是基本数据类型和 POJO 类型。输入参数映射过程类似于 JDBC 对 preparedStatement 对象设置参数的过程。 - 8、输出结果映射
输出结果类型可以是 Map、 List 等集合类型,也可以是基本数据类型和 POJO 类型。输出结果映射过程类似于 JDBC 对结果集的解析过程。
我们把Mybatis的功能架构分为三层:API接口层、数据处理层和基础支撑层。
- 1、API接口层
提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层来完成具体的数据处理。
MyBatis和数据库的交互有两种方式:
使用传统的MyBatis提供的API
使用Mapper接口
1)使用传统的MyBatis提供的API
这是传统的传递Statement Id 和查询参数给 SqlSession 对象,使用 SqlSession对象完成和数据库的交互;MyBatis 提供了非常方便和简单的API,供用户实现对数据库的增删改查数据操作,以及对数据库连接信息和MyBatis 自身配置信息的维护操作。
示例:
SqlSession session = sqlSessionFactory.openSession();
Category c = new Category();
c.setName("新增加的Category");
session.insert("addCategory",c);
上述使用MyBatis 的方法,是创建一个和数据库打交道的SqlSession对象,然后根据Statement Id 和参数来操作数据库,这种方式固然很简单和实用,但是它不符合面向对象语言的概念和面向接口编程的编程习惯。由于面向接口的编程是面向对象的大趋势,MyBatis 为了适应这一趋势,增加了第二种使用MyBatis 支持接口(Interface)调用方式。
2)使用Mapper接口
MyBatis 将配置文件中的每一个< mapper> 节点抽象为一个 Mapper 接口,而这个接口中声明的方法和跟< mapper> 节点中的
SqlSession session = sqlSessionFactory.openSession();
CategoryMapper mapper = session.getMapper(CategoryMapper.class);
List cs = mapper.list();
for (Category c : cs) {
System.out.println(c.getName());
}
根据MyBatis 的配置规范配置后,通过SqlSession.getMapper(XXXMapper.class) 方法,MyBatis 会根据相应的接口声明的方法信息,通过动态代理机制生成一个Mapper 实例,我们使用Mapper 接口的某一个方法时,MyBatis 会根据这个方法的方法名和参数类型,确定Statement Id,底层还是通过SqlSession.select(“statementId”,parameterObject);或者SqlSession.update(“statementId”,parameterObject); 等等来实现对数据库的操作
MyBatis 引用Mapper 接口这种调用方式,纯粹是为了满足面向接口编程的需要。(其实还有一个原因是在于,面向接口的编程,使得用户在接口上可以使用注解来配置SQL语句,这样就可以脱离XML配置文件,实现“0配置”)。
- 2、数据处理层
负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。
- 参数映射和动态SQL语句生成
动态语句生成可以说是MyBatis框架非常优雅的一个设计,MyBatis 通过传入的参数值,使用OGNL表达式来动态地构造SQL语句,使得MyBatis有很强的灵活性和扩展性。
参数映射指的是对于java 数据类型和jdbc数据类型之间的转换:这里有包括两个过程:查询阶段,我们要将java类型的数据,转换成jdbc类型的数据,通过 preparedStatement.setXXX() 来设值;另一个就是对resultset查询结果集的jdbcType 数据转换成java 数据类型。 - SQL语句的执行以及封装查询结果集成List< E>
动态SQL语句生成之后,MyBatis 将执行SQL语句,并将可能返回的结果集转换成List列表。MyBatis 在对结果集的处理中,支持结果集关系一对多和多对一的转换,并且有两种支持方式,一种为嵌套查询语句的查询,还有一种是嵌套结果集的查询。
- 3、基础支撑层
负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。
Mybatis有三种基本的Executor执行器,SimpleExecutor、ReuseExecutor、BatchExecutor。
- 1、SimpleExecutor
每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。 - 2、ReuseExecutor
执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。 - 3、BatchExecutor
执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。
作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。
指定Executor方式有两种:
- 一种是在配置文件中:
- 在获取sqlSession时设置,需要注意的时, 如果选择的是批量执行器时, 需要手工提交事务(默认不传参就是SimpleExecutor)
// 获取指定执行器的sqlSession SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH) // 获取批量执行器时, 需要手动提交事务 sqlSession.commit();1.14 Mybatis是否支持延迟加载
Mybatis仅支持association关联对象和collection关联集合对象的延迟加载,association指的就是一对一,collection指的就是一对多查询。在Mybatis配置文件中,可以配置是否启用延迟加载lazyLoadingEnabled=true|false。
延迟加载默认是打开的,如果需要关闭,需要在mybatis-config.xml中修改:
如果设置了全局加载,但是希望在某一个sql语句查询的时候不适用延时策略,可以添加如下属性:
fetchType,有效值为 lazy 和 eager。 指定属性后,将在映射中忽略全局配置参数 lazyLoadingEnabled,使用属性的值。
它的原理是,使用CGLIB创建目标对象的代理对象,当调用目标方法时,进入拦截器方法,比如调用a.getB().getName(),拦截器invoke()方法发现a.getB()是null值,那么就会单独发送事先保存好的查询关联B对象的sql,把B查询上来,然后调用a.setB(b),于是a的对象b属性就有值了,接着完成a.getB().getName()方法的调用。这就是延迟加载的基本原理。
1.15 一级缓存什么时候会失效一级缓存指的是sqlsession(回话)级别的缓存,关闭回话之后自动失效,默认情况下是开启的。会失效的情况:
- 1、不在同一个session中执行相同的sql语句;
- 2、当传递对象的时候,如果对象中的属性值不同,也不会走缓存;
- 3、在同一次查询过程中,如果数据库中数据发生了修改,那么缓存会失效。不同回话之间是不受影响的;
- 4、如果在一个会话过程中,手动清空了缓存(sqlSession.clearCache()),那么缓存也会失效。
二级缓存:表示的是全局缓存,必须要等到sqlsession关闭之后才会生效(这意味着二级缓存跟一级缓存中不会同时存在数据,因为二级缓存中的数据是在sqlsession 关闭之后才生效的)。默认是不开启的,如果需要开启的话,需要进行如下设置:
- 修改全局配置文件,在settings中添加配置。
- 指定在哪个映射文件中使用缓存的配置。
- 对应的java实体类必须要实现序列化的接口。
先查二级缓存,再查一级缓存,再查数据库。即使在一个sqlSession中,也会先查二级缓存;一个namespace中的查询更是如此。
1.18 如何获取自动生成的主键值1、先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库。
2、如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取。
3、一般不会关闭一级缓存。
4、二级缓存默认不开启。
5、如果二级缓存关闭,直接判断一级缓存是否有数据,如果没有就查数据库。
6、如果二级缓存开启,先判断二级缓存有没有数据,如果有就直接返回;如果没有,就查询一级缓存,如果有就返回,没有就查询数据库。
配置文件设置 usegeneratedkeys 为 true。实际上,在settings元素中设置useGeneratedKeys是一个全局参数,但是只会对接口映射器产生影响,对xml映射器不起效。示例:
1.19 Mybatis框架源码的大致流程
先根据总体的配置文件mybatis-config.xml来创建SqlSessionFactory,有了SqlSessionFactory就可以创建SqlSession。
再解析Mapper文件,构建出MappedStatement对象,该对象是对映射信息的封装,用于存储要映射的 SQL 语句的 id、参数等信息。
至于Mapper 接口的使用,其实就是通过MapperProxyFactory工厂,创建代理(JDK 动态代理)MapperMethod 对象,此时MappedStatement会封装成SqlCommand。在执行增删改查时,最终还是调用SqlSession去执行增删改查。
通过SqlSession去执行增删改查,会用到执行器Executor,执行器常见的几种:SimpleExecutor、ReuseExecutor、BatchExecutor。在使用Executor时,会用到StatementHandler。
先在数据库建一张表:表名为student,字段有id、name、score、age、gender。
在IDEA中建Maven项目,导入依赖:
mysql mysql-connector-java 5.1.10 org.mybatis mybatis 3.4.6 org.projectlombok lombok 1.18.12 provided junit junit 4.10 test
建实体类:
package com.test.po;
import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Student {
private Integer id;
private String name;
private Integer score;
private Integer age;
private Integer gender;
}
写Mapper文件:
SELECT * FROM student; INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender}); DELETE FROM student WHERe id = #{id};
写数据源properties文件:
db.url=jdbc:mysql://localhost:3306/yogurt?characterEncoding=utf8 db.user=root db.password=root db.driver=com.mysql.jdbc.Driver
写全局配置文件:
编写DAO类:
package com.test.dao;
import com.test.po.Student;
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;
import java.util.List;
public class StudentDao {
private SqlSessionFactory sqlSessionFactory;
public StudentDao(String configPath) throws IOException {
InputStream inputStream = Resources.getResourceAsStream(configPath);
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
}
public List findAll() {
SqlSession sqlSession = sqlSessionFactory.openSession();
List studentList = sqlSession.selectList("findAll");
sqlSession.close();
return studentList;
}
public int addStudent(Student student) {
SqlSession sqlSession = sqlSessionFactory.openSession();
int rowsAffected = sqlSession.insert("insert", student);
sqlSession.commit();
sqlSession.close();
return rowsAffected;
}
public int deleteStudent(int id) {
SqlSession sqlSession = sqlSessionFactory.openSession();
int rowsAffected = sqlSession.delete("delete",id);
sqlSession.commit();
sqlSession.close();
return rowsAffected;
}
}
测试类:
public class SimpleTest {
private StudentDao studentDao;
@Before
public void init() throws IOException {
studentDao = new StudentDao("mybatis-config.xml");
}
@Test
public void insertTest() {
Student student = new Student();
student.setName("yogurt");
student.setAge(24);
student.setGender(1);
student.setScore(100);
studentDao.addStudent(student);
}
@Test
public void findAllTest() {
List all = studentDao.findAll();
all.forEach(System.out::println);
}
}
结果示例:
上面的步骤:
2.1.2 mybatis-config.xml中的标签顺序
- 编写mapper.xml,书写SQL,并定义好SQL的输入参数,和输出参数;
- 编写全局配置文件,配置数据源,以及要加载的mapper.xml文件;
- 通过全局配置文件,创建SqlSessionFactory,再通过SqlSessionFactory创建一个SqlSession;
- 调用SqlSession上的selectOne,selectList,insert,delete,update等方法,传入mapper.xml中SQL标签的id,以及输入参数。
mybatis-config.xml中,各个标签要按照如下顺序进行配置,因为mybatis加载配置文件的源码中是按照这个顺序进行解析的:
- 1、
一般将数据源的信息单独放在一个properties文件中,然后用这个标签引入,在下面environment标签中,就可以用${}占位符快速获取数据源的信息。 - 2、
用来开启或关闭mybatis的一些特性,比如可以用来开启延迟加载,可以用 来开启二级缓存。 - 3、
在XXXMapper.xml中需要使用parameterType和resultType属性来配置SQL语句的输入参数类型和输出参数类型,类必须要写上全限定名,比如一个SQL的返回值映射为Student类,则resultType属性要写com.test.po.Student,可以用别名来简化书写,示例:
之后就可以在resultType上直接写student,mybatis会根据别名配置自动找到对应的类。当然,如果想要一次性给某个包下的所有类设置别名,可以用如下的方式:
如此,指定包下的所有类,都会以简单类名的小写形式,作为它的别名。
另外,对于基本的Java类型 -> 8大基本类型以及包装类,以及String类型,mybatis提供了默认的别名,别名为其简单类名的小写,比如原本需要写java.lang.String,其实可以简写为string。
- 4、
用于处理Java类型和Jdbc类型之间的转换,mybatis有许多内置的TypeHandler,比如StringTypeHandler,会处理Java类型String和Jdbc类型CHAR和VARCHAR。 - 5、
(了解即可)
mybatis会根据resultType或resultMap的属性来将查询得到的结果封装成对应的Java类,它有一个默认的DefaultObjectFactory,用于创建对象实例。 - 6、
可以用来配置mybatis的插件,比如在开发中经常需要对查询结果进行分页,就需要用到pageHelper分页插件,这些插件就是通过这个标签进行配置的。示例:
- 7、
配置数据源。 - 8、
用来配置XXXMapper.xml映射文件。
- 1、#{}是占位符,预编译处理;${}是拼接符,字符串替换,没有预编译处理。
Mybatis在处理#{}时,#{}传入参数是以字符串传入,会将SQL中的#{}替换为"?",调用PreparedStatement的set方法来赋值。
Mybatis在处理 $ {}时,就是把${}替换成变量的值。 - 2、#方式能够很大程度防止sql注入,原因在于预编译机制;$方式无法防止Sql注入。
- 3、$方式一般用于传入数据库对象,例如传入表名。
${}也是有自己的使用场景的,当需要传入动态的表名,列名的时候就需要使用${},就是最直接的拼接字符串的行为。
- 4、MyBatis排序时使用order by 动态参数时需要注意,用$而不是#。
- 5、一般能用#的就别用$。
如果入参类型是pojo,比如是Student类:
public class Student{
private String name;
private Integer age;
//...
}
#{name}表示取入参对象Student中的name属性,#{age}表示取age属性,这个过程是通过反射实现的。
${},一般会用在模糊查询的情景,比如SELECT * FROM student WHERe name like '%${name}%'。它的处理阶段在#{}之前,它不会做参数类型解析,而仅仅是做了字符串的拼接,若入参的Student对象的name属性为zhangsan,则上面那条SQL最终被解析为SELECt * FROM student WHERe name like '%zhangsan%'。如果此时用的是SELECt * FROM student WHERe name like '%#{name}%',这条SQL最终就会变成SELECt * FROM student WHERe name like '%'zhangsan'%',所以模糊查询只能用${}。
普通的入参也可以用${},但由于${}不会做类型解析,就存在SQL注入的风险,比如:
SELECt * FROM user WHERe name = '${name}' AND password = '${password}'
假如password属性为'OR '1' = '1,最终的SQL会变成:
SELECt * FROM user WHERe name = 'test' AND password = ''OR '1' = '1'
因为OR'1' = '1'恒成立,这个where条件就不起作用了。
对于简单类型(8种Java原始类型再加一个String)的入参,${}中参数的名字必须是value,示例:
SELECT count(1) FROM `user` WHERe name like '%${value}%'
上面的这种方式比较麻烦,为了简化开发,mybatis提供了mapper接口代理的开发方式,不需要再编写dao类,只需要编写一个mapper接口,一个mapper的接口和一个mapper.xml相对应,只需要调用SqlSession对象上的getMapper(),传入mapper接口的class信息,即可获得一个mapper代理对象,直接调用mapper接口中的方法,即相当于调用mapper.xml中的各个SQL标签,此时就不需要指定SQL标签的id字符串了,mapper接口中的一个方法,就对应了mapper.xml中的一个SQL标签。
2.3 XXXMapper.xml的使用全局配置文件和mapper.xml文件是最基本的配置,仍然需要。不过,这次不编写dao类,直接创建一个mapper接口。示例:
package com.test.mapper;
import com.test.po.Student;
import java.util.List;
public interface StudentMapper {
List findAll();
int insert(Student student);
int delete(Integer id);
List findByName(String value);
}
mapper.xml文件示例:
SELECT * FROM student; INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender}); DELETE FROM student WHERe id = #{id}; SELECT * FROM student WHERe name like '%${value}%';
mapper接口和mapper.xml之间需要遵循一定规则,才能成功的让mybatis将mapper接口和mapper.xml绑定起来:
- mapper接口的全限定名,要和mapper.xml的namespace属性一致;
- mapper接口中的方法名要和mapper.xml中的SQL标签的id一致;
- mapper接口中的方法入参类型,要和mapper.xml中SQL语句的入参类型一致;
- mapper接口中的方法出参类型,要和mapper.xml中SQL语句的返回值类型一致。
测试代码示例:
public class MapperProxyTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List studentList = mapper.findAll();
studentList.forEach(System.out::println);
}
}
结果示例:
基于这个mapper接口,mybatis会自动找到对应的mapper.xml,然后对mapper接口使用动态代理的方式生成一个代理类。
在没使用绑定接口时,需要用SqlSession来进行增删改查,示例:
student user = (student) session.selectOne("test.studentMapper.selectUserByID", 1);
接口绑定,就是在MyBatis中任意定义接口,然后把接口里面的方法和SQL语句绑定,直接调用接口方法就可以,这样比起原来了SqlSession提供的方法,有更加灵活的选择和设置。
接口绑定有两种实现方式:
- 1、通过注解绑定(不常用,耦合性高)
就是在接口的方法上面加上 @Select、@Update等注解,里面包含Sql语句来绑定;示例:
//这种方式不用写mapper.xml
@Select("select * from `tb_Teacher` where id = #{id}")
public teacher selectTeacherByID(int id);
- 2、通过xml里面写SQL来绑定(常用)
在这种情况下,要指定xml映射文件里面的namespace必须为接口的全路径名。示例:
select id,name,age,stuCountry stu_country from `tb_Student` where id = #{id}
当Sql语句比较简单时候,用注解绑定, 当SQL语句比较复杂时候,用xml绑定,一般用xml绑定的比较多。
2.5 模糊查询like语句该怎么写上文提到了模糊查询的一种写法,其实模糊查询的写法不止一种。
- 1)’%${question}%’ 可能引起SQL注入,不推荐。
- 2)"%"#{question}"%"注意:因为#{…}解析成sql语句时候,会在变量外侧自动加单引号’ ',所以这里 % 需要使用双引号" ",不能使用单引号 ’ ',不然会查不到任何结果。
- 3)CONCAt(’%’,#{question},’%’) 使用CONCAt()函数,推荐。
- 4)使用bind标签(不常用)
2.6 当实体类中的属性名和表中的字段名不一样时的处理方法select id,sex,age,username,password from person where username LIKE #{pattern}
- 1、写SQL语句时起别名
select id, first_name firstName, email,salary, dept_id deptID from employees where id = #{id}
在上面的例子中,first_name、dept_id是数据库表里的列名,firstName、deptID是实体类对应的属性名。
- 2、在Mapper映射文件中使用resultMap来自定义映射规则(常用)
select * from employees where id = #{id}
在上面的例子中,last_name、dept_id是数据库中表的列名,lastName、deptId是实体类的属性名。
2.7 使用MyBatis的mapper接口调用时的4个要求 1、Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
2、Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。
3、Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
4、Mapper.xml文件中的namespace即是mapper接口的类路径。 namespace示例:
2.8 Mybatis中不同的Xml映射文件,id是否可以重复select * from student where id=#{id}
不同的Xml映射文件,如果配置了namespace,那么id可以重复;如果没有配置namespace,那么id不能重复;毕竟namespace不是必须的,只是最佳实践而已。
原因:namespace+id是作为Map
xml文件中,namespace和id的示例:
2.9 代理主键和自然主键delete from emp where empno = #{empno}
- 自然主键是指事物属性中的自然唯一标示(例如身份证号);
- 代理主键是指与业务无关的,无意义的数字序列值;
- 在表设计时,优先推荐代理主键,不推荐自然主键(代理主键无意义,所以与业务解耦,另一方面,自然主键是自然界的事物,一般为字符串,处理麻烦)。
通常会将数据库表的主键id设为自增。在插入一条记录时,我们不设置其主键id,而让数据库自动生成该条记录的主键id,在插入一条记录后,有两种方式得到数据库自动生成的这条记录的主键id。
2.10.1 使用useGeneratedKeys和keyProperty属性示例:
INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender});
该方式适用于支持主键自增的数据库(Mysql、Sql server)。
有三个地方可以设置useGeneratedKeys=true参数:
- 1、在setting元素中设置 useGeneratedKeys参数
在setting元素中设置的useGeneratedKeys是一个全局的参数,但是只是对接口接口映射器产生影响,对xml映射器无效。示例:
代码中就可以获取ID了:
public interface TestMapper {
// 受全局useGeneratedKeys参数控制,添加记录之后将返回主键id
@Insert("insert into test(name,descr,url,create_time,update_time) values(#{name},#{descr},#{url},now(),now())")
Integer insertOneTest(Test test);
}
- 2、在XML映射器中配置useGeneratedKeys参数
示例:
insert into test(name,descr,url,create_time,update_time) values(#{name},#{descr},#{url},now(),now())
- 3、在接口映射器中设置useGeneratedKeys参数
示例:
// 设置useGeneratedKeys为true,返回数据库自动生成的记录主键id
@Options(useGeneratedKeys = true, keyProperty = "id", keyColumn = "id")
@Insert("insert into test(name,descr,url,create_time,update_time) values(#{name},#{descr},#{url},now(),now())")
Integer insertOneTest(Test test);
该类设置方式优先级高于第一种方式。
2.10.2 使用< selectKey >子标签示例:
INSERT INTO student (name,score,age,gender) VALUES (#{name},#{score},#{age},#{gender}); SELECT LAST_INSERT_ID();
如果使用的是Mysql这样的支持自增主键的数据库,可以简单的使用第一种方式;对于不支持自增主键的数据库,如Oracle,则没有主键返回这一概念,而需要在插入之前先生成一个主键。此时可以用
使用<selectKey>标签来获取主键的方式不仅适用于不提供主键自增功能的数据库,也适用于提供主键自增功能的数据库。
selectKey 元素描述:
获取主键示例:
public class MapperProxyTest {
private SqlSessionFactory sqlSessionFactory;
@Before
public void init() throws IOException {
InputStream resourceAsStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
}
@Test
public void test() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
Student student = new Student(-1, "Podman", 130, 15, 0);
mapper.insert(student);
sqlSession.commit();
System.out.println(student.getId());
}
}
2.11 批量查询
主要是动态SQL标签的使用,注意如果parameterType是List的话,则在标签体内引用这个List,只能用变量名list,如果parameterType是数组,则只能用变量名array。示例:
SELECT * FROM student AND id in #{id}
测试代码:
@Test
public void testBatchQuery() {
SqlSession sqlSession = sqlSessionFactory.openSession();
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
List students = mapper.batchFind(Arrays.asList(1, 2, 3, 7, 9));
students.forEach(System.out::println);
}
结果示例:
即根据具体的参数条件,来对SQL语句进行动态拼接。Mybatis里的主要动态标签如下:
- 1、if
当满足test条件时,才会将标签内的SQL语句拼接上去。示例:
SELECT * FROM student WHERe age >= 18 AND name like '%${name}%'
当有多个条件判断时,需要和where标签合起来使用。示例:
select * from emp empno > #{empno} and ename = #{ename}
- 2、choose
示例:
SELECT * FROM BLOG WHERe state = 'ACTIVE' AND title like #{title} AND author_name like #{author.name} AND featured = 1
- 3、where
标签只会在至少有一个子元素返回了SQL语句时,才会向SQL语句中添加WHERe,并且如果WHERe之后是以AND或OR开头,会自动将其删掉。示例:
SELECT * FROM BLOG state = #{state} AND title like #{title} AND author_name like #{author.name}
- 4、trim
标签可以用 标签代替。
trim标签一般用于去除sql语句中多余的and关键字,逗号,或者给sql语句前拼接 “where“、“set“以及“values(“ 等前缀,或者添加“)“等后缀,可用于选择性插入、更新、删除或者条件查询等操作。示例:
select * from emp
empno > #{empno} and
ename like #{ename} and
sal > #{sal} and
- 5、foreach
用来做迭代拼接的,通常会与SQL语句中的IN查询条件结合使用,注意,到parameterType为List(链表)或者Array(数组),后面在引用时,参数名必须为list或者array。如在foreach标签中,collection属性则为需要迭代的集合,由于入参是个List,所以参数名必须为list。示例:
SELECT * FROM student WHERe id in #{item}
一个较完整的例子。接口中的定义:
public ListselectEmpByDeptnos(@Param("deptnos") List deptnos);
EmpDao.xml中的示例:
select * from emp where deptno in
#{deptno}
测试代码:
EmpDao mapper = sqlSession.getMapper(EmpDao.class);
List list = mapper.selectEmpByDeptnos(Arrays.asList(10, 20));
- 6、sql
可将重复的SQL片段提取出来,然后在需要的地方,使用标签进行引用。示例:
2.13 Mybatis动态sql是做什么的SELECT * FROM user AND username like '%${user.name}%'
- 1、Mybatis 动态 sql 可以让我们在 Xml 映射文件内,以标签的形式编写动态 sql,完成逻辑判断和动态拼接 sql 的功能。
- 2、Mybatis 提供了 9 种动态 sql 标签:
trim|where|set|foreach|if|choose|when|otherwise|bind。 - 3、其执行原理为,使用 OGNL 从 sql 参数对象中计算表达式的值,根据表达式的值动态拼接 sql,以此来完成动态 sql 的功能。



