个人主页: Java程序鱼
如果文章对你有帮助,欢迎关注、点赞、收藏(一键三连)和订阅专栏
微信号:hzy1014211086,想加入技术交流群的小伙伴可以加我好友,群里会分享学习资料、学习方法
序号 | 内容 | 链接地址 |
---|---|---|
1 | Java基础知识面试题 | https://blog.csdn.net/qq_35620342/article/details/119636436 |
2 | Java集合容器面试题 | https://blog.csdn.net/qq_35620342/article/details/119947254 |
3 | Java并发编程面试题 | https://blog.csdn.net/qq_35620342/article/details/119977224 |
4 | Java异常面试题 | https://blog.csdn.net/qq_35620342/article/details/119977051 |
5 | JVM面试题 | https://blog.csdn.net/qq_35620342/article/details/119948989 |
6 | Java Web面试题 | https://blog.csdn.net/qq_35620342/article/details/119642114 |
7 | Spring面试题 | https://blog.csdn.net/qq_35620342/article/details/119956512 |
8 | Spring MVC面试题 | https://blog.csdn.net/qq_35620342/article/details/119965560 |
9 | Spring Boot面试题 | https://blog.csdn.net/qq_35620342/article/details/120333717 |
10 | MyBatis面试题 | https://blog.csdn.net/qq_35620342/article/details/119956541 |
11 | Spring Cloud面试题 | 待分享 |
12 | Redis面试题 | https://blog.csdn.net/qq_35620342/article/details/119575020 |
13 | MySQL数据库面试题 | https://blog.csdn.net/qq_35620342/article/details/119930887 |
14 | RabbitMQ面试题 | 待分享 |
15 | Dubbo面试题 | 待分享 |
16 | Linux面试题 | 待分享 |
17 | Tomcat面试题 | 待分享 |
18 | ZooKeeper面试题 | 待分享 |
19 | Netty面试题 | 待分享 |
20 | 数据结构与算法面试题 | 待分享 |
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。
ORM(Object Relational Mapping),对象关系映射,是一种为了解决关系型数据库数据与简单Java对象(POJO)的映射关系的技术。简单的说,ORM是通过使用描述对象和数据库之间映射的元数据,将程序中的对象自动持久化到关系型数据库中。
Hibernate属于全自动ORM映射工具,使用Hibernate查询关联对象或者关联集合对象时,可以根据对象关系模型直接获取,所以它是全自动的。
而Mybatis在查询关联对象或关联集合对象时,需要手动编写sql来完成,所以,称之为半自动ORM映射工具。
JDBC是Java连接关系数据库的底层API,利用JDBC开发持久层存在诸多问题,而MyBatis正是为了解决JDBC的以下问题而生的。
1、数据库连接创建、释放频繁造成系统资源浪费从而影响系统性能,如果使用数据库链接池可解决此问题。
解决:在mybatis-config.xml中配置数据链接池,使用连接池管理数据库连接。
2、Sql语句在代码中硬编码,造成代码不易维护,实际应用sql变化的可能较大,sql变动需要改变java代码。
解决:将Sql语句配置在XXXXmapper.xml文件中与java代码分离。
3、使用PreparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护。
解决: Mybatis自动将java对象映射至sql语句。
4、对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据库记录封装成pojo对象解析比较方便。
解决:Mybatis自动将sql执行结果映射至java对象。
MyBatis和Hibernate不同,它不完全是一个ORM框架,因为MyBatis需要程序员自己编写Sql语句,不过Mybatis可以通过XML或注解方式灵活配置要运行的sql语句,并将java对象和sql语句映射生成最终执行的sql,最后将sql执行的结果再映射生成java对象。
MyBatis学习门槛低,简单易学,程序员直接编写原生态sql,可严格控制sql执行性能,灵活度高,非常适合对关系数据模型要求不高的软件开发,例如互联网软件、企业运营类软件等,因为这类软件需求变化频繁,一但需求变化要求成果输出迅速。但是灵活的前提是mybatis无法做到数据库无关性,如果需要实现支持多种数据库的软件则需要自定义多套sql映射文件,工作量大。
Hibernate对象/关系映射能力强,对于关系模型要求高的软件(例如需求固定的定制化软件)如果用Hibernate开发可以节省很多代码,提高效率。但是Hibernate的学习门槛高,要精通门槛更高,而且怎么设计O/R映射,在性能和对象模型之间如何权衡,以及怎样用好Hibernate需要具有很强的经验和能力才行。
优点
与传统的数据库访问技术相比,ORM有以下优点:
缺点
(1)获取SqlSessionFactory对象
解析文件的每一个信息保存在Configuration中,返回包含Configuration的DefaultSqlSession;
注意:【MappedStatement】:代表一个增删改查的详细信息
(2)获取SqlSession对象
返回一个DefaultSqlSession对象,包含Executor和Configuration;
这一步会创建Executor;
(3)获取接口的代理对象(MapperProxy)
getMapper,使用MapperProxyFactory创建一个MapperProxy的代理对象,代理对象里包含了DefaultSqlSession(Executor)
(4)执行增删改查方法
(5)关闭会话
在学习 MyBatis 程序之前,需要了解一下 MyBatis 工作原理,以便于理解程序。MyBatis 的工作原理如下图
我们把Mybatis的功能架构分为三层:
这张图从上往下看。MyBatis的初始化,会从mybatis-config.xml配置文件,解析构造成Configuration这个类,就是图中的红框。
(1)加载配置:配置来源于两个地方,一处是配置文件,一处是Java代码的注解,将SQL的配置信息加载成为一个个MappedStatement对象(包括了传入参数映射配置、执行的SQL语句、结果映射配置),存储在内存中。
(2)SQL解析:当API接口层接收到调用请求时,会接收到传入SQL的ID和传入对象(可以是Map、JavaBean或者基本数据类型),Mybatis会根据SQL的ID找到对应的MappedStatement,然后根据传入参数对象对MappedStatement进行解析,解析后可以得到最终要执行的SQL语句和参数。
(3)SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
(4)结果映射:将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
1.定义:
SQL 预编译指的是数据库驱动在发送 SQL 语句和参数给 DBMS 之前对 SQL 语句进行编译,这样 DBMS 执行 SQL 时,就不需要重新编译。
2.为什么需要预编译
JDBC 中使用对象 PreparedStatement 来抽象预编译语句,使用预编译。预编译阶段可以优化 SQL 的执行。预编译之后的 SQL 多数情况下可以直接执行,DBMS 不需要再次编译,越复杂的SQL,编译的复杂度将越大,预编译阶段可以合并多次操作为一个操作。同时预编译语句对象可以重复利用。把一个 SQL 预编译后产生的 PreparedStatement 对象缓存下来,下次对于同一个SQL,可以直接使用这个缓存的 PreparedState 对象。Mybatis默认情况下,将对所有的 SQL 进行预编译。
在Mybatis配置文件中,在设置(settings)可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数,如SqlSession openSession(ExecutorType execType)。
配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。
在实际使用中,我们会经常性的涉及到多表联合查询,但是有时候,并不会立即用到所有的查询结果,我来举两个例子:
针对这样一种情况,延迟加载这一种机制就出现了,延迟加载(懒加载)顾名思义,就是对某种信息推迟加载,这样的技术也就帮助我们实现了 “按需查询” 的机制,在一对多,或者多对多的情况下
既然提到了延迟加载,当然顺便提一句立即加载,它的含义就是不管是否用户需要,一调用,则马上查询,这种方式,适合与多对一,或者一对一的情况下。
首先,配置基本的环境,然后我们首先在数据库准备两张表
User表
CREATE TABLE USER (
`id` INT(11)NOT NULL AUTO_INCREMENT,
`username` VARCHAR(32) NOT NULL COMMENT '用户名',
`telephone` VARCHAR(11) NOT NULL COMMENT '手机',
`birthday` DATETIME DEFAULT NULL COMMENT '生日',
`gender` CHAR(1) DEFAULT NULL COMMENT '性别',
`address` VARCHAR(256) DEFAULT NULL COMMENT '地址',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
Account表
CREATE TABLE `account` (
`ID` int(11) NOT NULL COMMENT '编号',
`UID` int(11) default NULL COMMENT '用户编号',
`MONEY` double default NULL COMMENT '金额',
PRIMARY KEY (`ID`),
KEY `FK_Reference_8` (`UID`),
CONSTRAINT `FK_Reference_8` FOREIGN KEY (`UID`) REFERENCES `user` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然后分别创建出其对应的实体类
User类
public class User implements Serializable {
private Integer id;
private String username;
private String telephone;
private Date birthday;
private String gender;
private String address;
//一对多关系映射,主表实体应该包含从表实体的集合引用
private List<Account> accounts;
...... 请补充 get set 和 toString 方法
}
Account类
public class Account implements Serializable {
private Integer id;
private Integer uid;
private Double money;
//从表实体应该包含一个主表实体的对象引用
private User user;
...... 请补充 get set 和 toString 方法
}
UserMapper.xml
两个接口中创建对应方法
public interface AccountMapper {
/**
* 查询所有账户
* @return
*/
List<Account> findAll();
}
public interface UserMapper {
/**
* 查询所有用户信息,同时显示出该用户下的所有账户
*
* @return
*/
List<User> findAll();
/**
* 根据id查询用户信息
* @param userId
* @return
*/
User findById(Integer userId);
}
延迟加载代码实现
首先,给大家演示一下,我们之前一对一查询用户的方式,同时会将用户对应所有的账户信息,也查询出来
/**
* 测试查询所有
*/
@Test
public void testFindAll() {
List<User> users= userMapper.findAll();
for (User user : users) {
System.out.println("---------------------");
System.out.println(user);
System.out.println(user.getAccounts());
}
}
这种方式是通过 SQL 语句,以及resultMap将 用户和账户的信息同时查询出来
那么如何实现我们上面所说的延迟加载呢?
这次我们选择 查询账户,然后延迟加载用户的信息
修改AccountMapper.xml
首先需要修改的就是账户的映射配置文件,可以看到我们在查询时,依旧定义了一个 resultMap 先封装了 Account ,然后通过association 进行关联 User,其中使用的就是 select 和 column 实现了延迟加载用户信息
<mapper namespace="cn.ideal.mapper.AccountMapper">
<resultMap id="userAccountMap" type="Account">
<id property="id" column="id">id>
<result property="uid" column="uid">result>
<result property="money" column="money">result>
<association property="user" column="uid" javaType="User" select="cn.ideal.mapper.UserMapper.findById">association>
resultMap>
<select id="findAll" resultMap="userAccountMap">
SELECT * FROM account
select>
mapper>
第一次测试代码
我们只执行一下账户的查询所有方法,看一下,是否能够实现我们的效果
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
}
执行效果
可以看到,三条 SQL 语句都执行了,这是为什么呢?
这是因为,我们在测试方法之前,需要开启延迟加载功能
延迟加载功能
经过查阅文档,我们知道了,如果想要开始延迟加载功能,就需要在总配置文件 SqlMapConfig.xml 中配置 setting 属性,也就是将延迟加载 lazyLoadingEnable 的开关设置成 teue ,由于是按需加载,所以还需要将积极加载修改为消极加载,也就是将 aggressiveLazyLoading 改为 false
当然,由于我这里导入的 MyBatis 版本为 3.4.5 所以这个值默认就是 false 实际上不用设置也可以,不过我们还是写出来
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false">setting>
settings>
注意:如果有使用typeAliases配置别名的话一定要将 typeAliases 标签放在后面
再次测试
仍然只执行查询方法
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
}
这一次果然只执行了一条查询 account 的命令
那么当用户想要查看到,每个账户对应下的用户的时候呢?这也就是按需查询,只需要在测试时,加入对应获取方法就可以了
@Test
public void testFindAll(){
List<Account> accounts = accountMapper.findAll();
for (Account account : accounts){
System.out.println("----------------------------");
System.out.println(account);
System.out.println(account.getUser());
}
}
#{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)
就 是 字 符 串 替 换 。 直 接 替 换 掉 占 位 符 。 {} 就是字符串替换。直接替换掉占位符。 就是字符串替换。直接替换掉占位符。方式一般用于传入数据库对象,例如传入表名.
使用 ${} 的话会导致 sql 注入。什么是 SQL 注入呢?比如 select * from user where id = ${value}
value 应该是一个数值吧。然后如果对方传过来的是 001 and name = tom。这样不就相当于多加了一个条件嘛?把SQL语句直接写进来了。如果是攻击性的语句呢?001;drop table user,直接把表给删了
所以为了防止 SQL 注入,能用 #{} 的不要去用 ${}
如果非要用 ${} 的话,那要注意防止 SQL 注入问题,可以手动判定传入的变量,进行过滤,一般 SQL 注入会输入很长的一条 SQL 语句
方式1:$ 这种方式,简单,但是无法防止SQL注入,所以不推荐使用
LIKE '%${name}%'
方式2:#
LIKE "%"#{name}"%"
方式3:字符串拼接
AND name LIKE CONCAT(CONCAT('%',#{name},'%'))
方式4:bind标签
方式5:java代码里写
param.setUsername("%CD%"); 在 java 代码中传参的时候直接写上
AND username LIKE #{username}
然后 mapper 里面直接写 #{} 就可以了
方法一:使用map接口传递参数
严格来说,map适用几乎所有场景,但是我们用得不多。原因有两个:首先,map是一个键值对应的集合,使用者要通过阅读它的键,才能明了其作用;其次,使用map不能限定其传递的数据类型,因此业务性质不强,可读性差,使用者要读懂代码才能知道需要传递什么参数给它,所以不推荐用这种方式传递多个参数。
public List findRolesByMap(Map parameterMap);
方法二:使用注解传递多个参数
MyBatis为开发者提供了一个注解@Param(org.apache.ibatis.annotations.Param),可以通过它去定义映射器的参数名称,使用它可以得到更好的可读性 这个时候需要修改映射文件的代码,此时并不需要给出parameterType属性,让MyBatis自动探索便可以了 使可读性大大提高,使用者也方便了,但是这会带来一个麻烦。如果SQL很复杂,拥有大于10个参数,那么接口方法的参数个数就多了,使用起来就很不容易,不过不必担心,MyBatis还提供传递Java Bean的形式。
public List findRolesByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
方法三:通过Java Bean传递多个参数
public List findRolesByBean(RoleParams roleParam);
方法四:混合使用
在某些情况下可能需要混合使用几种方法来传递参数。举个例子,查询一个角色,可以通过角色名称和备注进行查询,与此同时还需要支持分页
public List findByMix(@Param("params") RoleParams roleParams, @Param("page") PageParam PageParam);
Mybatis常会出现批量操作,如批量查询,批量插入,批量修改(replace into)。批量操作要比循环执行效率提升很多,这里对mybatis的批量操作做一个总结讲解。
(1)Foreach
foreach:foreach的主要用在构建in条件中,它可以在SQL语句中进行迭代一个集合。foreach元素的属性主要有 item,index,collection,open,separator,close。它是批量操作的核心标签,下面都是foreach在不同场景的应用和写法。
List
select id, accid, key1, key1createtime key1CreateTime, key2, key2createtime key2CreateTime
from m_acc_keys where accid in
#{item}
注意:你可以传递一个 List 实例或者数组作为参数对象传给 MyBatis。当你这么做的时候,MyBatis 会自动将它包装在一个 Map 中,用名称在作为键。List 实例将会以“list”作为键,而数组实例将会以“array”作为键。
List
这是一个批量保存(插入或修改)的例子
replace into xx (id, c1,c2) values
(#{it.id},#{it.c1},#{it.c2})
replace:会根据主键和唯一索引判断该记录是否存在,存在就删除在插入(修改),不存在就插入(insert)。
(2)数组
还是一个查询的例子
public List selectByIds(String[] ids);
(3)多重循环
colids和proids是两个平级的数据。
insert into b_column_programme(columnId, programmeId)
values
(#{colid}, #{proid})
(4)批量插入id自增长
Mybatis在版本3.4.x以上支持批量插入绑定自增长id,常用版本3.4.1。
对应的maven支持
org.mybatis
mybatis
3.4.1
org.mybatis
mybatis-spring
1.3.1
Mapper.xml写法
insert into xx
(c1,c2)
values
(#{it.c1},#{it.c2})
MySQL:Mapper 文件 insert 语句设置
useGeneratedKeys="true" keyProperty="id"
Oracle:Mapper 文件 insert 语句增加
select xxx_SEQ.nextval from dual
第1种: 通过在查询的SQL语句中定义字段名的别名,让字段名的别名和实体类的属性名一致。
第2种: 通过
来映射字段名和实体类属性名的一一对应的关系。
第一种:接口实现类继承 SqlSessionDaoSupport:使用此种方法需要编写mapper 接口,mapper 接口实现类、mapper.xml 文件。
(1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置
(2)定义 mapper 接口
(3)实现类集成 SqlSessionDaoSupport
mapper 方法中可以 this.getSqlSession()进行数据增删改查。
(4)spring 配置
第二种:使用 org.mybatis.spring.mapper.MapperFactoryBean:
(1)在 sqlMapConfig.xml 中配置 mapper.xml 的位置,如果 mapper.xml 和mappre 接口的名称相同且在同一个目录,这里可以不用配置
(2)定义 mapper 接口:
(3)mapper.xml 中的 namespace 为 mapper 接口的地址
(4)mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致
(5)Spring 中定义
第三种:使用 mapper 扫描器:
(1)mapper.xml 文件编写:
mapper.xml 中的 namespace 为 mapper 接口的地址;
mapper 接口中的方法名和 mapper.xml 中的定义的 statement 的 id 保持一致;
如果将 mapper.xml 和 mapper 接口的名称保持一致则不用在 sqlMapConfig.xml中进行配置。
(2)定义 mapper 接口:
注意 mapper.xml 的文件名和 mapper 的接口名称保持一致,且放在同一个目录
(3)配置 mapper 扫描器:
(4)使用扫描器后从 spring 容器中获取 mapper 的实现对象。
接口绑定有两种方式
(1)使用注解,在接口的方法上面添加@Select@Update等注解,里面写上对应的SQL语句进行SQL语句的绑定。
(2)通过映射文件xml方式进行绑定,指定xml映射文件中的namespace对应的接口的全路径名
什么时候用注解绑定?什么时候用xml绑定?
当SQL语句比较简单的时候,使用注解绑定就可以了,当SQL语句比较复杂的话,使用xml方式绑定,一般用xml方式绑定比较多
Mapper接口方法名和mapper.xml中定义的每个sql的id相同。
Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql 的parameterType的类型相同。
Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同。
Mapper.xml文件中的namespace即是mapper接口的类路径。
答:不能重载,方法名对应的 mapper.xml 文件里的一个 id,这个与方法名对应,系统会根据 namespace+id 找到对应的方法对应。
Dao 接口即 Mapper 接口。接口的全限名,就是映射文件中的 namespace 的值;接口的方法名,就是映射文件中 Mapper 的 Statement 的 id 值;接口方法内的参数,就是传递给 sql 的参数。Mapper 接口是没有实现类的,当调用接口方法时,接口全限名+方法名拼接字符串作为 key 值,可唯一定位一个 MapperStatement。在 Mybatis 中,每一个标签,都会被解析为一个MapperStatement 对象。
举例:com.mybatis3.mappers.StudentDao.findStudentById,可以唯一找到 namespace 为 com.mybatis3.mappers.StudentDao 下面 id 为findStudentById 的 MapperStatement。
Mapper 接口里的方法,是不能重载的,因为是使用 全限名+方法名 的保存和寻找策略。Mapper 接口的工作原理是 JDK 动态代理,Mybatis 运行时会使用 JDK动态代理为 Mapper 接口生成代理对象 proxy,代理对象会拦截接口方法,转而执行 MapperStatement 所代表的 sql,然后将 sql 执行结果返回。
可以重复,但是需要映射文件的namespace不同
不同的 Xml 映射文件,如果配置了 namespace,那么 id 可以重复;如果没有配置 namespace,那么 id 不能重复。
原因就是 namespace+id 是作为 Map
有了 namespace,自然 id 就可以重复,namespace 不同,namespace+id 自然也就不同。
答:Mybatis将所有Xml配置信息都封装到All-In-One重量级对象Configuration内部。在Xml映射文件中,
标签会被解析为ParameterMap对象,其每个子元素会被解析为ParameterMapping对象。
标签会被解析为ResultMap对象,其每个子元素会被解析为ResultMapping对象。每一个、
、
、
标签均会被解析为MappedStatement对象,标签内的sql会被解析为BoundSql对象。
第一种是使用
标签,逐一定义列名和对象属性名之间的映射关系。
第二种是使用sql列的别名功能,将列别名书写为对象属性名,比如T_NAME AS NAME,对象属性名一般是name,小写,但是列名不区分大小写,Mybatis会忽略列名大小写,智能找到与之对应对象属性名,你甚至可以写成T_NAME AS NaMe,Mybatis一样可以正常工作。
有了列名与属性名的映射关系后,Mybatis通过反射创建对象,同时使用反射给对象的属性逐一赋值并返回,那些找不到映射关系的属性,是无法完成赋值的。
还有很多其他的标签,
、
、
、
、
,加上动态sql的9个标签,trim|where|set|foreach|if|choose|when|otherwise|bind等,其中
为sql片段标签,通过
标签引入sql片段,
为不支持自增的主键生成策略标签。
虽然 Mybatis 解析 Xml 映射文件是按照顺序解析的,但是,被引用的 B 标签依然可以定义在任何地方,Mybatis 都可以正确识别。
原理:
Mybatis 解析 A 标签时,发现引用了 B 标签,未解析到 B 标签,此时会把 A 标签标记为未解析状态;
继续解析下面内容,把剩下解析完之后,再解析标记为未解析的标签;
此时已解析到 B 标签,此时再解析A标签时,B标签已经存在,A 标签也就顺利解析完成。
有联合查询和嵌套查询。联合查询是几个表联合查询,只查询一次,通过在resultMap里面的association,collection节点配置一对一,一对多的类就可以完成
嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过配置association,collection,但另外一个表的查询通过select节点配置。
Mybatis可以映射枚举类,不单可以映射枚举类,Mybatis可以映射任何对象到表的一列上。映射方式为自定义一个TypeHandler,实现TypeHandler的setParameter()和getResult()接口方法。
TypeHandler有两个作用,一是完成从javaType至jdbcType的转换,二是完成jdbcType至javaType的转换,体现为setParameter()和getResult()两个方法,分别代表设置sql问号占位符参数和获取列查询结果。
Mybatis动态sql可以让我们在Xml映射文件内,以标签的形式编写动态sql,完成逻辑判断和动态拼接sql的功能,Mybatis提供了9种动态sql标签trim|where|set|foreach|if|choose|when|otherwise|bind。
其执行原理为,使用OGNL从sql参数对象中计算表达式的值,根据表达式的值动态拼接sql,以此来完成动态sql的功能。
(1)原始方法,使用 limit,需要自己处理分页逻辑:
对于 mysql 数据库可以使用 limit ,如:
select * from table limit 5; --返回前5行
select * from table limit 0,5; --同上,返回前5行
select * from table limit 5,10; --返回6-15行
对于 oracle 数据库可以使用 rownum ,如:从表Sys_option(主键为sys_id)中从第10条记录开始检索20条记录,语句如下:
SELECT * FROM (SELECT ROWNUM R,t1.* From Sys_option where rownum < 30 ) t2
Where t2.R >= 10
(2)拦截StatementHandler,其实质还是在最后生成limit语句
(3)使用PageHelper插件,这是目前比较常见的方法
Mybatis仅可以编写针对ParameterHandler、ResultSetHandler、StatementHandler、Executor这4种接口的插件,Mybatis使用JDK的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这4种接口对象的方法时,就会进入拦截方法,具体就是InvocationHandler的invoke()方法,当然,只会拦截那些你指定需要拦截的方法。
实现Mybatis的Interceptor接口并复写intercept()方法,然后在给插件编写注解,指定要拦截哪一个接口的哪些方法即可,记住,别忘了在配置文件中配置你编写的插件。
缓存机制减轻数据库压力,提高数据库性能
mybatis的缓存分为两级:一级缓存、二级缓存
(1)一级缓存:
一级缓存为 sqlsesson 缓存,缓存的数据只在 SqlSession 内有效。在操作数据库的时候需要先创建 SqlSession 会话对象,在对象中有一个 HashMap 用于存储缓存数据,此 HashMap 是当前会话对象私有的,别的 SqlSession 会话对象无法访问。
具体流程:
第一次执行 select 完毕会将查到的数据写入 SqlSession 内的 HashMap 中缓存起来
第二次执行 select 会从缓存中查数据,如果 select 同传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
注意:
1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 SqlSession 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异
2、当一个 SqlSession 结束后那么他里面的一级缓存也就不存在了, mybatis 默认是开启一级缓存,不需要配置
3、 mybatis 的缓存是基于 [namespace:sql语句:参数] 来进行缓存的,意思就是, SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key ,查询返回的语句作为 value 保存的
(2)二级缓存:
二级缓存是 mapper 级别的缓存,也就是同一个 namespace 的 mapper.xml ,当多个 SqlSession 使用同一个 Mapper 操作数据库的时候,得到的数据会缓存在同一个二级缓存区域
二级缓存默认是没有开启的。需要在 setting 全局参数中配置开启二级缓存
开启二级缓存步骤:
1、conf.xml 配置全局变量开启二级缓存
默认是false:关闭二级缓存
2、在 userMapper.xml 中配置
当前mapper下所有语句开启二级缓存
这里配置了一个 LRU 缓存,并每隔60秒刷新,最大存储512个对象,而返回的对象是只读的
若想禁用当前select语句的二级缓存,添加 useCache="false"修改如下:
具体流程:
1.当一个 sqlseesion 执行了一次 select 后,在关闭此 session 的时候,会将查询结果缓存到二级缓存
2.当另一个 sqlsession 执行 select 时,首先会在他自己的一级缓存中找,如果没找到,就回去二级缓存中找,找到了就返回,就不用去数据库了,从而减少了数据库压力提高了性能
注意:
1、如果 SqlSession 执行了 DML 操作(insert、update、delete),并 commit 了,那么 mybatis 就会清空当前 mapper 缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现差异
2、 mybatis 的缓存是基于 [namespace:sql语句:参数] 来进行缓存的,意思就是,SqlSession 的 HashMap 存储缓存数据时,是使用 [namespace:sql:参数] 作为 key ,查询返回的语句作为 value 保存的。