# 如何使用Mybatis #
## 1.简单使用 ##
### 1.导入mybatis和数据库连接依赖 ###
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.16</version>
</dependency>
### 2.编写mybatis的全局配置文件 ###
<?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">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=GMT%2B8"/>
<property name="username" value="mybatis"/>
<property name="password" value="mybatis"/>
</dataSource>
</environment>
</environments>
<!-- 将对象和数据的映射sql文件注册到全局配置文件中 -->
<mappers>
<mapper resource="EmployeeMapper.xml"/>
</mappers>
</configuration>
### 3.编写对象和数据的映射关系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">
<!--
namespace:命名空间,可随意指定,全局唯一,如果使用接口式编程则为接口的全类名
id:可随意指定,该xml文件中唯一,如果使用接口式编程则为接口中的方法名
resultType:返回值的类型
-->
<mapper namespace="com.zhangbao.mapper.EmployeeMapper">
<select id="getEmployeeById" resultType="com.zhangbao.bean.Employee">
select id, last_name as lastName, email, gender from employee where id = #{id}
</select>
</mapper>
### 4.建立数据所映射的对象 ###
//下面三个注解为lombok注解,可省略get,set,构造器方法等
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private String id;
private String lastName;
private String email;
private int gender;
}
### 5.执行sql语句: ###
1.获取SqlSessionFactory
private SqlSessionFactory getSqlSessionFactory() throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
return sqlSessionFactory;
}
2.根据SqlSessionFactory获取SqlSession
SqlSession session = sqlSessionFactory.openSession()
3.执行Sql语句:
Employee employee = session.selectOne("com.zhangbao.mapper.EmployeeMapper.getEmployeeById", 1);
其中selectOne的第一个参数是EmployeeMapper.xml文件中的namespace.id
第二个参数是执行该sql语句需要的参数
4.缺点
1.每个语句都需要使用namespace.id来确定sql语句,容易写错
2.参数可以传任意值,没有数据格式约束
### 6.使用接口式编程 ###
1.创建一个EmployeeMapper接口
public interface EmployeeMapper {
/**
* 根据id获取员工对象
* @param id
* @return
*/
Employee getEmployeeById(Integer id);
}
2.将EmployeeMapper接口和EmployeeMapper.xml进行绑定
1.修改EmployeeMapper.xml的namespace为接口全类名
2.将EmployeeMapper.xml的id改为接口中的方法名
3.同步骤5中的第一小步,获取SqlSessionFactory
4.同步骤5中的第二小步,根据SqlSessionFactory获取SqlSession
5.根据SqlSession获取Mapper对象
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
6.调用mapper中的方法,根据方法调用EmployeeMapper.xml文件中的id与方法相同的sql语句
Employee employee = mapper.getEmployeeById(1);
7.此时可以对方法参数类型进行约束,也跳过了易错的危险
### 7.小的总结 ###
1.SqlSession代表和数据库的一次会话,用完必须关闭
2.SqlSession和Connection一样都是线程非安全的,每次使用都应该去获取新的对象
3.mapper接口没有实现类,但是mybatis会为这个接口生成一个代理对象,用来将mapper接口和xml文件进行绑定
4.两个重要的配置文件:
mybatis的全局配置文件:包括数据库连接池信息,事务管理器信息等
sql映射文件:保存了每一个sql语句的映射信息
## 2.mapper文件 ##
### 1.mybatis中传递的参数取值 ###
#### 1.传递的是单个值 ####
Employee getEmployeeById(Integer id);
xml中直接使用#{参数名}来取出参数值,但其实mybatis没有对其进行封装,使用#{任意名}都可以取出来
select * from employee where id = #{id}
#### 2.传递的是多个参数 ####
Employee getEmployeeById(Integer id, String lastName);
mybatis对其进行封装成map,key是param1...paramN,value是传递的参数值,所以不能使用#{参数名}来获取值,可以使用#{param1}来获取第一个参数值等
select * from employee where id = #{param1} and last_name = #{param2}
但是可以对其进行命名,使用@Param("参数名")来将其重命名,这样mybatis在将其封装成map时,key就是重命名的参数名,value是参数值
Employee getEmployeeById(@Param("id")Integer id, @Param("lastName")String lastName);
select * from employee where id = #{id} and last_name = #{lastName}
也可以将多个参数封装成一个对象传递过去
Employee getEmployeeById(Employee employee);
使用时直接使用#{属性名}就可以从对象中取出参数值
select * from employee where id = #{id} and last_name = #{lastName}
或者可以直接将多个参数组成一个map传递过去
Employee getEmployeeById(Map map);
使用时直接使用#{属性名}就可以从map中取出参数值
select * from employee where id = #{id} and last_name = #{lastName}
#### 3.如果传递的参数是Collection(list,set)或者数组 ####
这种情况mybatis也会特殊处理,将其封装到map中,不过key值根据类型不同也会不同
如果是Collection,那么key值就是collection
如果精确到List,那么key就是list
如果是数组,那么key就是array
所以:
Employee getEmployeeById(List<Integer> ids);
如果想要取第一个id的值
select * from employee where id = #{list[0]}
#### 4.参数封装流程 ####
Employee employee = mapper.getEmployeeById(1);
//由于mapper是一个接口,所以mybatis会帮我们生成一个代理对象
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
//代理对象执行sql语句
return mapperMethod.execute(sqlSession, args);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//判断sql语句类型
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
//封装参数
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
public Object getNamedParams(Object[] args) {
//names在创建ParamNameResolver对象时已经初始化好了
final int paramCount = names.size();
//如果传递的参数为null,直接返回null
if (args == null || paramCount == 0) {
return null;
//如果没有param注解并且只有一个参数,直接返回该参数
} else if (!hasParamAnnotation && paramCount == 1) {
return args[names.firstKey()];
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
//key为names的value,value为args的值
param.put(entry.getValue(), args[entry.getKey()]);
// 创建一个key为param+序列号的名字
final String genericParamName = GENERIC_NAME_PREFIX + String.valueOf(i + 1);
// 将其保存在param集合中,可以使用param注解的name来获取value也可以使用param序列值来获取value
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
//创建ParamNameResolver对象时初始化了names属性
public ParamNameResolver(Configuration config, Method method) {
final Class<?>[] paramTypes = method.getParameterTypes();
final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<Integer, String> map = new TreeMap<>();
int paramCount = paramAnnotations.length;
// 从@Param 注解构造names
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
if (isSpecialParameter(paramTypes[paramIndex])) {
// skip special parameters
continue;
}
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
//如果有@param注解,则获取注解的name值
if (annotation instanceof Param) {
hasParamAnnotation = true;
name = ((Param) annotation).value();
break;
}
}
//如果没有获取到name值
if (name == null) {
// 查看mybatis-config.xml是否配置了useActualParamName参数,该配置允许使用方法签名中的名称作为语句参数名称,但是必须是jdk1.8及其以上
if (config.isUseActualParamName()) {
name = getActualParamName(method, paramIndex);
}
//如果还是没有name,则以序列号来作为参数名
if (name == null) {
name = String.valueOf(map.size());
}
}
//根据序号和name存入map中
map.put(paramIndex, name);
}
names = Collections.unmodifiableSortedMap(map);
}
#### 5.不同的取值方式 ####
#{}:以预编译的方式将参数设置到sql语句中,防止sql注入
${}:取出的值直接拼装在sql语句中,会有安全问题,适用于动态的查询表名,字段名,排序字段名等
#### 6.#{}取值时设置的字段 ####
主要有一个jdbcType需要在特定条件下设置
mybatis中默认的全局配置xml文件中jdbcTypeForNull属性的默认值是OTHER,在Oracle数据库中,如果传递的参数是null,那么取值时默认是OTHER类型,而Oracle默认不认识OTHER类型,所以会报无效的数据类型错误,mysql支持OTHER类型,所以在使用Oracle环境时需要在使用#{}取值时需要设置jdbcType属性
解决方案(2种):
1.使用#{}取值时设置jdbcType=null
2.在全局配置文件mybatis-config.xml文件中设置
<setting name="jdbcTypeForNull" value="NULL"/>
#### 7.返回值是List ####
如果查询的返回值是list集合,resultType=集合中元素的类型
#### 8.查询的返回值是Map ####
如果想要查询的一条数据的返回值是Map,key是字段名,value是字段值,则resultType=map
如果查询的是多条记录map,key是主键值,value是对象,则resultType=value的类型,但是需要给方法添加一个注解:@MapKey("对象中作为map的key的字段名")
#### 9.使用resultMap自定义返回封装规则 ####
如果查询返回的值不符合自己的预期,可以自定义返回规则
id="emp":以后使用emp引用该resultMap
type:映射的javaBean
column:数据库中的表的字段
property:和javaBean中的字段映射
<resultMap id="empMap" type="com.zhangbao.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
...
</resultMap>
#### 10.查询出来的对象有关联对象 ####
可以使用下面三种resultMap自定义返回封装规则
<resultMap id="empMap1" type="com.zhangbao.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--association:用于嵌套对象,javaType:嵌套的对象类型必填-->
<association property="department" javaType="com.zhangbao.bean.Department">
<id column="d_id" property="id"/>
<result column="department_name" property="departmentName"/>
</association>
</resultMap>
<resultMap id="empMap2" type="com.zhangbao.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--直接引用该对象的关联对象属性-->
<result column="d_id" property="department.id"/>
<result column="department_name" property="department.departmentName"/>
</resultMap>
//分步查询
<resultMap id="empMap3" type="com.zhangbao.bean.Employee">
<id column="id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
<!--关联对象:使用select指定的方法(传入column指定的这列参数的值)查出对象并封装给property select的语句由命名空间加方法id组成,column是外层查询语句查询出的数据字段,可以写为d_id, 如果是多个字段需要写成map形式,如{id=d_id}其中的id是getDeptById这个sql语句的查询条件字段,fetchType默认为lazy,默认是保持延时加载,可以修改为eager变为立刻加载-->
<association property="department"
select="com.zhangbao.mapper.EmployeeMapper.getDeptById"
column="d_id" fetchType="lazy">
</association>
</resultMap>
//使用empMap1和empMap2的语句
<select id="getEmployeeById" resultMap="empMap2">
select e.id,e.last_name,e.email,e.gender,d.id as d_id,d.department_name
from employee e, department d where e.id = #{id} and e.d_id = d.id;
</select>
//使用empMap3的语句
<select id="getEmployeeById" resultMap="empMap3">
select * from employee where id = #{id}
</select>
<select id="getDeptById" resultType="com.zhangbao.bean.Department">
select * from department where id = #{id}
</select>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Employee {
private String id;
private String lastName;
private String email;
private int gender;
private Department department;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
private Integer id;
private String departmentName;
}
#### 11.延时加载 ####
分步查询中默认情况下嵌套对象会直接查询出来,如果想要使用到嵌套对象再将其查询出来时需要在全局配置文件mybatis-config.xml文件中配置两个属性
<!--当开启时,所有关联对象都会延迟加载-->
<setting name="lazyLoadingEnabled" value="true"/>
<!--当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载-->
<setting name="aggressiveLazyLoading" value="false"/>
全局已经开启了延时加载,如果只是想某个分步方法不使用延时加载可以在分步的sql方法上使用fetchType="eager"来取消延时加载
#### 12.查询的关联对象是list集合 ####
<resultMap id="deptMap" type="com.zhangbao.bean.Department">
<id column="id" property="id"/>
<result column="department_name" property="departmentName"/>
<!--查询的关联对象是list集合则需要使用collection,ofType是集合内元素类型-->
<collection property="employees" ofType="com.zhangbao.bean.Employee">
<result column="e_id" property="id"/>
<result column="last_name" property="lastName"/>
<result column="email" property="email"/>
<result column="gender" property="gender"/>
</collection>
</resultMap>
<select id="getDeptList" resultMap="deptMap">
SELECT d.id, d.department_name, e.id e_id, e.last_name, e.email, e.gender
FROM department d JOIN employee e ON d.id = e.d_id WHERE d.id = #{id}
</select>
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Department {
private Integer id;
private String departmentName;
private List<Employee> employees;
}
分步查询和延时加载和步骤10与步骤11一样
## 3.mybatis中的缓存 ##
### 1.一级缓存: ###
1.什么是一级缓存:
一级缓存(本地缓存):sqlSession级别的缓存(Map).一级缓存是一直开启的.
与数据库同一次会话期间查询到的数据会放在本地缓存中
以后如果要获取相同的数据会直接从缓存中获取,不会再查询数据库
2.一级缓存失效情况:
1.SqlSession不同
2.两次查询条件不一样
3.两次查询之间有增删改操作
4.手动清理一级缓存:session.clearCache
### 2.二级缓存 ###
1.什么是二级缓存:
二级缓存(全局缓存):xxxMapper.xml级别的缓存,每个Mapper.xml文件都有一个二级缓存
一次会话查询到的数据放在一级缓存中,会话结束,一级缓存也会消失,但查询到的数据会放在数据对应的mapper二级缓存中,比如一个session查询了emp和dept,会话结束,emp会被放在empMapper的二级缓存中,dept会放在deptMapper的二级缓存中
2.怎么开启二级缓存:
1.在全局配置文件mybatis-config.xml中全局地开启或关闭所有映射器已经配置的任何缓存。默认为true
<setting name="cacheEnabled" value="true"/>
2.在mapper文件中配置使用cache
<cache></cache>
其中有多个属性:eviction="LRU" flushInterval="" readOnly="" size="" type=""
eviction:缓存的回收策略
LRU:最近最少使用的,移除最长时间不被使用的对象
FIFO:先进先出,按对象进入缓存的顺序清除缓存
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极的移除基于垃圾回收器状态和弱引用规则的对象
默认是LRU
flushInterval:缓存刷新间隔
缓存多长时间清空一次,默认不清空,单位为毫秒
readOnly:是否只读
true:mybatis认为所有从缓存中获取数据都是只读操作,不会修改数据.mybatis为了加快获取速度,直接就会将数据在缓存中的引用交给用户,速度快但不安全
false:mybatis认为获取的数据会被修改,mybatis会利用序列化和反序列化的技术克隆一份数据交给用户,安全但是速度略慢
默认是false,非只读
size:缓存存放多少元素
type:指定自定义缓存的全类名,实现Cache接口的全类名
3.数据和对象的映射类需要实现序列化接口
3.缓存的配置有什么作用:
1.全局配置文件mybatis-config.xml中cacheEnabled如果设置为false,只影响二级缓存,一级缓存仍照常工作
2.mapper.xml文件中每个select标签都有一个userCache属性,默认为true,如果改为false,也只影响二级缓存,一级缓存仍可以继续缓存
3.mapper.xml文件中每个增删改操作标签中都有一个flushCache标签,默认为true,一级缓存和二级缓存都会被清除.select标签中flushCache默认为false
4.session的clearCache方法清除的是当前session的一级缓存,与二级缓存没关系
5.全局配置文件mybatis-config.xml中localCacheScope如果设置为SESSION表示一级缓存启用,如果设置为STATEMENT表示禁用缓存,默认为SESSION
4.缓存的原理机制:
1.每个SqlSession都有一个一级缓存,每个会话查询的数据都保存在对应的一级缓存中
2.每个mapper(xxxMapper.xml)都有一个二级缓存
3.当SqlSession会话关闭后,一级缓存会将查询出来的数据根据不同的mapper(xxxMapper.xml)将数据放到对应的二级缓存中
4.当新的会话进来,会先查询二级缓存,如果二级缓存没有再从一级缓存中查询,还没有才会查询数据库
## 4.mybatis工作原理 ##
### 1.获取SqlSessionFactory ###
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
1.build方法做了什么:
//inputStream流是mybats-config.xml全局配置文件得到的
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
//创建一个xml解析器
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
2.parser.parse方法:
public Configuration parse() {
//第一次进入parsed为false
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//evalNode("/configuration")是获取mybats-config.xml文件的configuration节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
3.parseConfiguration方法:
private void parseConfiguration(XNode root) {
try {
//读取properties标签
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
//获取所有的settings属性存入到configuration对象中
settingsElement(settings);
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//将mappers标签中的所有mapper添加到mapperRegistry中
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
4.mapperElement方法:
private void mapperElement(XNode parent) throws Exception {
//读取mappers标签的内容
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
//如果不是package标签
} else {
//获取标签中属性字段
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
//在configuration中添加mapper信息
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
5.mapperParser.parse();
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//configuration中添加mapper信息
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//绑定mapper和命名空间并将mapper注册到mapperRegistry中
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
6.configurationElement方法:
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
//创建MappedStatement放入configuration中
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
7.buildStatementFromContext:
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//创建MappedStatement
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
8.statementParser.parseStatementNode创建MappedStatement并放入configuration中
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
9.所以步骤2结束时的configuration中已经有了全局配置文件和所有mapper配置文件中所有配置和sql语句,属性等.
10.步骤1最后根据configuration返回了一个DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
11.此时SqlSessionFactory创建完毕
### 2.获取SqlSession ###
SqlSession session = sqlSessionFactory.openSession()
1.获取session
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//从configuration中获取环境配置
final Environment environment = configuration.getEnvironment();
//根据环境创建事务管理器
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//创建事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建sql语句执行器
final Executor executor = configuration.newExecutor(tx, execType);
//创建DefaultSqlSession
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2.根据不同类型创建Sql语句执行器Executor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//如果开启了二级缓存,将执行器包装为缓存执行器
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
3.创建DefaultSqlSession
new DefaultSqlSession(configuration, executor, autoCommit);
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
### 3.根据SqlSession获取Mapper对象 ###
EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
1.在创建SqlSessionFactory时就将所有的mapper对象放入到mapperRegistry中.
1.获取SqlSessionFactory步骤1.4:
configuration.addMappers(mapperPackage);
configuration.addMapper(mapperInterface);
2.获取SqlSessionFactory步骤1.5:
bindMapperForNamespace();
configuration的addMapper和addMappers方法:
public void addMappers(String packageName) {
mapperRegistry.addMappers(packageName);
}
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
mapperRegistry的addMapper方法:
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
//判断是否已经有了该mapper
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//添加一个代理mapper对象
knownMappers.put(type, new MapperProxyFactory<>(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
2.获取mapper
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
//从mapperRegistry中获取mapper
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
3.由于注册进mapperRegistry时是代理对象工厂
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//先取出代理对象工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//创建代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
4.返回创建的MapperProxy代理对象
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
### 4.执行调用数据库方法 ###
Employee employee = mapper.getEmpById(1);
1.被代理对象拦截执行invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断被执行的方法是否是Object类中的方法
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//获取或创建一个MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行sql
return mapperMethod.execute(sqlSession, args);
}
2.根据sql的类型不同执行不同的sql语句
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
//调用DefaultSqlSession查询方法
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
3,执行DefaultSqlSession的selectOne方法
public <T> T selectOne(String statement, Object parameter) {
// 查询数据
List<T> list = this.selectList(statement, parameter);
//如果查询的数据是一个则返回第一个
if (list.size() == 1) {
return list.get();
//如果查询的数据多于1个则抛异常
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//从configuration中获取MapperStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//将封装好的参数传递进来执行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
//封装参数
private Object wrapCollection(final Object object) {
//如果是Collection类型则map中存入collection
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<>();
map.put("collection", object);
//如果是list类型再存入一个list
if (object instanceof List) {
map.put("list", object);
}
return map;
//如果是数组则存入array
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<>();
map.put("array", object);
return map;
}
return object;
}
4.CachingExecutor执行query方法
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
//创建一个key值,如果使用缓存则使用key值从缓存中查找或者将数据使用该key值存入缓存
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//获取二级缓存Cache
Cache cache = ms.getCache();
if (cache != null) {
//是否需要清除缓存
flushCacheIfRequired(ms);
//如果使用缓存
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//从缓存中获取值
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//如果缓存中没有值则从数据库查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//从数据库中获取数据
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
5.调用BaseExecutor的query从一级缓存或数据库中获取数据
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//是否清除一级缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//本地缓存中获取数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从数据库中获取数据
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
6.从数据库中查询数据
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//先将key放入本地缓存
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//查询数据
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//将本地中空缓存删除
localCache.removeObject(key);
}
//将数据放入本地缓存中
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
7.mybatis本质是使用StatementHandler创建Statement执行sql语句
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
//获取configuration对象
Configuration configuration = ms.getConfiguration();
//创建StatementHandler对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
//返回查询的对象
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
8.创建StatementHandler对象
//创建StatementHandler对象
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//创建StatementHandler对象
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//调用拦截器链执行拦截器的interceptor方法,使用插件接入此原理
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//根据不同类型创建不同的StatementHandler对象,mapper.xml映射文件中每个语句都有一个statementType属性,该属性默认是PREPARED
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
//所以默认会创建一个该对象
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
//创建StatementHandler对象时做了什么
public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
}
//创建StatementHandler对象时会创建ParameterHandler对象和ResultSetHandler对象
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
//创建ParameterHandler对象
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//创建ResultSetHandler对象
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
9.mybatis运行时创建了四个对象:
1.Executor:sql语句执行器
2.StatementHandler:sql语句执行器
3.ParameterHandler:参数处理器,处理sql语句占位符的参数填充
4.ResultSetHandler:数据返回处理器
### 5.使用插件自定义执行 ###
1.创建插件类,实现Interceptor接口,并使用注解确定拦截的处理器和方法
@Intercepts({
//type:想要拦截哪种处理器的类型
//method:拦截这个处理器的那个方法
//方法可能有重载,所以需要填写方法参数确保方法
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
})
public class StatementHandlerPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//执行目标对象的目标方法
Object proceed = invocation.proceed();
System.out.println("执行目标对象的目标方法");
return proceed;
}
@Override
public Object plugin(Object target) {
//封装一个代理对象返回
Object wrap = Plugin.wrap(target, this);
System.out.println("将目标对象封装成一个代理对象返回");
return wrap;
}
@Override
public void setProperties(Properties properties) {
System.out.println("自定义插件参数: " + properties);
}
}
2.在mybatis-config.xml全局配置文件中配置插件
<plugins>
<plugin interceptor="com.zhangbao.plugin.StatementHandlerPlugin">
<!--可以配置参数在插件类中使用-->
<property name="username" value="root"/>
<property name="password" value="root"/>
</plugin>
</plugins>
3.如果有多个插件,则全局配置顺序创建代理对象,而且是前面的插件被后面的插件包裹后创建成代理对象。后面的插件创建的代理对象的目标对象其实是前面的插件所创建的代理对象。运行目标对象的方法时先运行的是配置插件的逆序