1,几个核心对象
1.1, SqlSessionFactory
- SqlSessionFactory通过类名可以看出其作用是通过工厂模式创建SqlSession.
- 先看看SqlSessionFactory的类结构和相关的方法。SqlSessionFactory实现了有两个子类分别是DefaultSqlSessionFactory和SqlSessionManager.
- 其中,DefaultSqlSessionFactory是构建Sqlsession最常用的一个类,可以直接通过构造器方式进行实例化,也可以使用mybatis提供的SqlSessionFactoryBuilder来构建一个SqlSessionFactory.
- SqlSessionFactory重载了多个openSession()方法,其目的就是希望实例化出一个Sqlsession,并且通过不同的参数形式构建出不同功能的Sqlsession,可以为即将实例化出的SqlSession设置事务隔离等级,执行器类型,是否自动提交等.
1.2, SqlSession对象
1.3, 类型转换器TypeHandler
当我们借助mybatis将对象更新到数据库,或者从数据库获取数据,然后映射到内存中的对象时,有一个非常关键的步骤.
就是把jdbc数据类型与java对象的数据类型相互转换,这种转换在MyBatis中会借助TypeHandler类型处理器实现。
MyBatis中已提供了很多内置的类型转换器(参考TypeHandlerRegistry.class),如:BooleanTypeHandler,ByteTypeHandler,ShortTypeHandler 等…
在TypeHandlerRegistry类中进行注册就行,但当我们业务中需要一些特殊的类型转换时,还是需要自定义类型转换器。
1.4, 拦截器Interceptor
Mybatis中提供一种插件应用机制,本质上就是采用责任链模式InterceptorChain,通过动态代理使用多个拦截器,形成拦截器链,为目标对象添加更多扩展功能,例如对所有dao接口方法做一个日志记录和接口耗时记录.
拦截器对象编写,配置及创建:
- 实现拦截器接口Interceptor,并对其要拦截的对象进行描述
- 在mybatis核心配置文件中通过plugin标签,配置自定义拦截器
- Mybatis框架初始化时,会创建拦截器对象然后添加到拦截器链,一起执行
拦截器对象应用过程分析:
Mybatis 会在Executor,StatementHandler,ResultSetHandler或ParameterHandler对象创建时,
假如定义了拦截器,就会为指定对象创建代理对象,并在执行代理对象业务方法时,执行Interceptor对象的intercept方法。
例如:Executor对象创建时的时序图如下:
案例实现:基于拦截器实现分页插件,并掌握市场上PageInterceptor插件的应用原理,大多数是拦截器来实现的。
1.5, 插件Plugin
我们也可以自己定义一个基于代理对象实现的插件功能.这种思想和Spring的AOP是一样的.
2, Mybatis的设计模式
2.1, 建造者模式
- SqlSessionFactoryBuilder :创建SqlSessionFactory,进而产生SqlSession
- XMLStatementBuilder: 解析mybatis的映射文件,构建SQL相关的信息,并封装给MapperedStatement
- XMLConfigBuilder: 解析mybatis配置文件来构建Configuration,读取配置文件信息
2.2, 工厂模式
- SqlSessionFactory
- TransactionFactory
2.3, 责任链模式
- InterceptorChain : 拦截器链,由多个拦截器形成(如: List),可以结合拦截器和动态代理来完成自定义插件.
2.4, 代理模式
- UserMapper mapper = sqlSession.getMapper(UserMapper.class);//JDK产生代理对象,可以参考DefaultSqlSession.class
3, Mybatis的缓存
3.1,一级缓存
Mybatis 一级缓存应用特点:
- 一级缓存默认开启,不需要配置,其生命周期和SqlSession一致
- 其本质就是,在内部设计是一个没有容量限定的HashMap(k,v),key就是statement的标识,value就是查到的数据
3.2,二级缓存
- 二级缓存是SqlSessionFactory级别的,
通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取 - 使用步骤:
a. 在MyBatis的配置文件中开启二级缓存(默认开启)
<setting name="cacheEnabled" value="true"/>
1、true:只读缓存,会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势
2、false:读写缓存,会返回缓存对象的拷贝(通过序列化),这会慢一些,但是安全,因此默认是false
b. 在MyBatis的映射文件XML中配置cache
<cache/>
cache标签用于声明这个namespace使用二级缓存,并且可以自定义配置。
- Mybatis二级缓存应用特点:
a. 二级缓存允许SqlSession之间缓存数据的共享,同时粒度更加的细,能够到namespace级别,通过Cache接口实现类不同的组合,对Cache的可控性也更强。
b. 在多表查询时,二级缓存可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻。
c. 默认的MyBatis Cache实现都是基于本地的,分布式环境下会出现读取到脏数据,建议直接使用Redis,Memcached等分布式缓存可能成本更低,安全性也更高。
d. 注意SqlSession关闭之后,一级缓存中的数据才会写入二级缓存
e. 查询顺序:先查询二级缓存,如果二级缓存没有命中,再查询一级缓存, 如果一级缓存也没有命中,则查询数据库
4, ResultMap的复杂使用
4.1,对象间的关系
分为一对一和一对多,分别使用resultMap提供的不同方案来处理:
一对一:使用association + javaType
一对多:使用collection + ofType
4.2,pom.xml的编写
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.2</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>mybatis</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>mybatis</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.4</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
4.3,准备数据库表
create table user_info(
id int primary key auto_increment,
user_name varchar(100),
user_addr varchar(200),
user_age int);
insert into user_info values(null,'韩梅梅','上海',20);
insert into user_info values(null,'王海涛','北京',30);
insert into user_info values(null,'张慎政','河南',10);
create table user_extra(
id int primary key auto_increment,
user_id int,
work varchar(100),
salary double);
insert into user_extra values(null,'1','程序员',100000);
insert into user_extra values(null, '2','教师',1000);
insert into user_extra values(null, '3','CTO',100000);
4.4,准备实体类
UserInfo.java
package domain;
import java.io.Serializable;
public class UserInfo implements Serializable {
private static final long serialVersionUID = 1182073125744165828L;
private UserExtra userExtra;
public UserExtra getUserExtra() {
return userExtra;
}
public void setUserExtra(UserExtra userExtra) {
this.userExtra = userExtra;
}
private Integer id;
private String userName;
private String userAddr;
private Integer userAge;
public UserInfo() {
}
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;
}
public String getUserAddr() {
return userAddr;
}
public void setUserAddr(String userAddr) {
this.userAddr = userAddr;
}
@Override
public String toString() {
return "UserInfo{" +
"userExtra=" + userExtra +
", id=" + id +
", userName='" + userName + '\'' +
", userAddr='" + userAddr + '\'' +
", userAge=" + userAge +
'}';
}
public Integer getUserAge() {
return userAge;
}
public void setUserAge(Integer userAge) {
this.userAge = userAge;
}
public UserInfo(Integer id, String userName, String userAddr, Integer userAge) {
this.id = id;
this.userName = userName;
this.userAddr = userAddr;
this.userAge = userAge;
}
}
UserExtra.java
package domain;
import java.io.Serializable;
public class UserExtra implements Serializable {
private static final long serialVersionUID = -2054928697926972055L;
private Integer id;
private Integer userId;
private String work;
private Double salary;
@Override
public String toString() {
return "UserExtra{" +
"id=" + id +
", userId=" + userId +
", work='" + work + '\'' +
", salary=" + salary +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getWork() {
return work;
}
public void setWork(String work) {
this.work = work;
}
public Double getSalary() {
return salary;
}
public void setSalary(Double salary) {
this.salary = salary;
}
public UserExtra() {
}
public UserExtra(Integer id, Integer userId, String work, Double salary) {
this.id = id;
this.userId = userId;
this.work = work;
this.salary = salary;
}
}
4.5,一对一
4.5.1 需求
user_extra 和 user_info表是一对一的关系
根据用户查询一对一关系的用户扩展信息,使用association + javaType描述关联表的信息
4.5.2 改造UserInfo类
package cn.tedu.pojo;
import java.util.List;
public class UserInfo {
private int id;
private String userName;
private String userAddr;
private int userAge;
**//一对一
private UserExtra userExtra;
public UserExtra getUserExtra() {
return userExtra;
}
public void setUserExtra(UserExtra userExtra) {
this.userExtra = userExtra;
}**
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserAddr() {
return userAddr;
}
public void setUserAddr(String userAddr) {
this.userAddr = userAddr;
}
public int getUserAge() {
return userAge;
}
public void setUserAge(int userAge) {
this.userAge = userAge;
}
**@Override
public String toString() {
return "UserInfo [id=" + id + ", userName=" + userName + ", userAddr=" + userAddr + ", userAge=" + userAge
+ ", userExtra=" + userExtra + "]";
}**
}
4.5.3 创建UserInfoMapper.xml
<?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="mapper.UserInfoDao">
<!-- 一对一: 固定搭配association+javaType,如果命名规则是有规则的,就很简单了-->
<resultMap id="UIUERM" type="domain.UserInfo" autoMapping="true">
<!--UserInfo类里,一对一的UserExtra,property写属性名,javaType写属性的类型-->
<association property="userExtra" javaType="domain.UserExtra" autoMapping="true">
</association>
</resultMap>
<select id="getInfo" resultMap="UIUERM">
select * from user_info a,user_extra b
where a.id=b.user_id
and a.id=#{id}
</select>
</mapper>
4.5.4 引入sqlMapConfig.xml
<?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">
<!-- mybatis的核心配置文件 -->
<configuration>
<!-- 配置别名,在Mapper.xml文件里就可以直接用别名了,不用写那么全 -->
<typeAliases>
<typeAlias type="cn.tedu.pojo.Dept" alias="Dept"/>
<typeAlias type="cn.tedu.pojo.Emp" alias="Emp"/>
<typeAlias type="cn.tedu.pojo.UserInfo" alias="UserInfo"/>
<typeAlias type="cn.tedu.pojo.UserExtra" alias="UserExtra"/>
</typeAliases>
<environments default="test">
<environment id="test">
<transactionManager type="JDBC"></transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatisdb?characterEncoding=utf8&serverTimezone=Asia/Shanghai" />
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
<mapper resource="mappers/DeptMapper.xml"/>
<mapper resource="mappers/EmpMapper.xml"/>
<mapper resource="mappers/UserInfoMapper.xml"/>
</mappers>
</configuration>
4.5.5 创建UserInfoDao接口
package mapper;
import domain.UserInfo;
public interface UserInfoDao {
//根据id获取用户信息,并关联查询出用户的扩展信息
UserInfo getInfo(Integer id);
}
4.5.6 测试类
package test;
import domain.UserInfo;
import mapper.UserInfoDao;
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 org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
public class TestRM {
SqlSessionFactory factory ;
@Before
public void init() throws IOException {
InputStream in = Resources.getResourceAsStream("mybatis.xml");
factory = new SqlSessionFactoryBuilder().build(in);
}
@Test
public void selectAll(){
SqlSession session = factory.openSession();
UserInfoDao dao = session.getMapper(UserInfoDao.class);
//根据用户编号,查出用户信息和关联的用户扩展信息
UserInfo info = dao.getInfo(2);
//UserInfo{userExtra=UserExtra{id=2, userId=2, work='教师', salary=1000.0}
// , id=2, userName='王海涛', userAddr='北京', userAge=30}
System.out.println("info = " + info);
}
}