第一部分
概述
传统jdbc执行步骤
- 使用JDBC编程需要连接数据库,注册驱动和数据库信息。
- 操作Connection,打开Statement对象。
- 通过Statement执行SQL,返回结果到ResultSet对象。
- 使用ResultSet读取数据,然后通过代码转化为具体的POJO对象。
- 关闭数据库连接资源
传统JDBC的弊端
-
工作量相对较大
打开连接,关闭连接,手动封装结果集
-
异常捕获
hibernate的缺点
- 全表映射带来的不便,比如更新时需要发送所有的字段
- 无法根据不同的条件组装不同的SQL
- 对多表关联和复杂SQL查询支持较差,需要自己写SQL,返回后需要自己将数据组装为POJO.
- 不能有效支持存储过程
- 虽然有HQL,但是性能较差。
使用
mybatis入门实例
新建mybatis demo项目
新建maven项目
编辑pom.xml
引入mybatis包和mysql连接包
<groupId>com.itcv.mybatis.demo</groupId>
<artifactId>mybatisDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.7</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>6.0.5</version>
</dependency>
</dependencies>
新建mybatis-config.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">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.0.125:3306/empi?characterEncoding=utf8"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/MatchRulesMapper.xml"/>
</mappers>
</configuration>
新建MatchRuleMapper.xml
<select id="maxSortNum" resultType="java.lang.Integer">
select max(sort_num) from match_rules where yn =0
</select>
新建MatchRuleMapper.java
public interface MatchRulesMapper {
int deleteByPrimaryKey(Integer id);
int insert(MatchRules record);
Integer maxSortNum();
}
新建MatchRule.java
public class MatchRules {
/**
*
*/
private Integer id;
/**
* 规则名称
*/
private String ruleName;
/**
* 规则类型
*/
private Integer ruleType;
/**
* 序列号
*/
private Integer sortNum;
/**
* 逻辑删除标志 0.正常 1.已删除
*/
private Byte yn;
/**
* 创建时间
*/
private Date createTime;
/**
* 更新时间
*/
private Date updateTime;
/**
* 匹配规则中文描述
*/
private String ruleTypeStr;
/**
* 规则字段
*/
private String ruleField;
/**
* 规则状态0.启用 1.禁用
*/
private Byte ruleState;
}
新建测试入口
public class Demo {
public static void main(String[] args) {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
MatchRulesMapper mapper = sqlSession.getMapper(MatchRulesMapper.class);
Integer maxNum = mapper.maxSortNum();
System.out.println(maxNum);
} catch (IOException e) {
e.printStackTrace();
}
}
}
整体项目目录
String resource = "mybatis-config.xml";
//读取配置文件的数据库连接,相应的xml
InputStream inputStream = Resources.getResourceAsStream(resource);
//生成SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//打开SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//获取接口
MatchRulesMapper mapper = sqlSession.getMapper(MatchRulesMapper.class);
//执行
Integer maxNum = mapper.maxSortNum();
总结
-
短短6行代码就能执行sql并返回结果了,吃惊
-
执行SQL步骤
- 读取配置文件
- 创建SqlSessionFactory
- 创建SqlSession
- 获取接口
- 执行方法返回结果
疑问
没有定义接口的实现方法怎么就返回结果了呢?
mybatis组件
SqlSessionFactoryBuilder(构造器)
它会根据配置信息或者代码来生成SqlSessionFactory.
SqlSessionFactory
依靠工厂来生成SqlSession(会话)
SqlSession
是一个既可以发送SQL去执行并返回结果,也可以获取Mapper的接口
Sql Mapper
它是Mybatis新设计的组件,它是由一个Java接口和XML文件(或注解)构成的,需要给出对应的SQL和映射规则。它负责发送SQL去执行,并返回结果。
生命周期
SqlSessionFactoryBuilder
SqlSessionFactoryBuilder是利用XML或者Java编码获得资源来构建SqlSessionFactory的,通过它可以构建多个SessionFactory.它的作用就是一个构建器,一旦我们构建了SqlSessionFactory,它的作用就已经完结,失去了存在的意义。
SqlSessionFactory
SqlSessionFactory的作用是创建SqlSession,而SqlSession就是一个会话,相当于JDBC中的Connection对象。每次应用程序访问数据库都需要通过SqlSessionFactory创建SqlSession,所以SqlSessionFactory应该存在Mybatis应用的整个生命周期中。而如果我们多次创建同一个数据库的SqlSessionFactory,则每次创建SqlSessionFactory会打开更多的数据库连接资源,那么连接资源就很快被耗尽。
SqlSession
SqlSession是一个会话,相当于JDBC的一个Connection对象,它的生命周期是在请求数据库处理事务的过程中。它是一个线程不安全的对象,在涉及多线程的时候我们要特别的当心,操作数据库需要注意隔离级别,数据库锁等高级特性。
Mapper
Mapper是一个接口,没有任何实现类,它的作用是发送SQL,然后返回我们需要的结果,因此它应该在一个SqlSession事务方法之内,是一个方法级别的东西。
Mybatis配置
environments配置环境
配置环境可以注册多个数据源,每一个数据源分为两大部分,一个是数据源的配置,另外一个是数据库事务的配置。
<environments default="development"> <!-- 默认数据源-->
<environment id="development">
<transactionManager type="JDBC"> <!--数据库事务-->
<property name="autoCommit" value="false"/> <!--数据源不自动提交-->
</transactionManager>
<dataSource type="POOLED"> <!--数据库连接方式-->
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.0.125:3306/empi?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
数据库事务
transactionManager配置的是数据库事务,其中type有三种配置方式。
- JDBC,采用JDBC方式管理事务,在独立编码中常常使用。
- MANAGED,采用容器方式管理事务,在JNDI数据源中常用。
- 自定义,由使用者自定义数据库事务管理办法,适用于特殊应用。
数据源连接信息
Mybatis提供了4种配置方式:
- UNPOOLED,非连接池,使用UnpooledDataSource实现
- POOLED,连接池,使用PooledDataSource实现
- JNDI,JNDI数据源,使用JndiDataSourceFactory来获取数据源
- 自定义数据源
引入映射器的方法
- 用文件路径引入映射器
<mappers>
<mapper resource="mapper/MatchRulesMapper.xml"/>
</mappers>
- 用包名引入映射器
<mappers>
<package name="com.itcv.mybatis.demo.dao"/>
</mappers>
- 用类注册引入映射器
<mappers>
<mapper class="com.itcv.mybatis.demo.dao.MatchRulesMapper"/>
</mappers>
- 用userMapper.xml映入映射器
<mappers>
<mapper url="file:////D:\doc\studay\gitee\mybatis-3.5.7\mybatisDemo\src\main\resources\mapper"/>
</mappers>
映射器
select元素
自动映射
只要返回的SQL列名和JavaBean的属性一致,MyBatis就会自动回填这些字段无需任何配置。
autoMappingBehavior 参数 默认值PARTIAL
- NONE,取消自动映射。
- PARTIAL,只会自动映射,没有定义嵌套结果集映射的结果集。默认值
- FULL,会自动映射任意复杂的结果集
传递多个参数
使用Map传递参数
<select id ="findRoleByMap" parameterType="map" resultMap ="roleMap">
select id,role_name,note from t_role
where role_name like concat('%',#{roleName},'%')
and note like concat('%',#{note},'%')
</select>
List<Role> findRoleByMap(Map<String,String> params);
Map<String,String> paramsMap = new HashMap<String,String>();
paramsMap.put("roleName","me");
paramsMap.put("note","no");
roleMapper.findRoleByMap(paramsMap);
这个方法虽然简单,但是可读性差
使用注解方式传递参数
List<Role> findRoleByAnnotation(@Param("roleName") String rolename, @Param("note") String note);
<select id="findRoleByAnnotation" resultMap="roleMap">
select id, role_name, note from t_role
where role_name like concat ('%',#{roleName},'%')
and note like concat('%',#{note},'%')
</select>
使用JavaBean传递参数
public class RoleParam {
private String roleName;
private String note;
get/set ....省略
}
<select id ="findRoleByParms" parameterType="com.itcv.demo.RoleParam" resultMap="roleMap">
select id, role_name, note from t_role
where role_name like concat ('%',#{roleName},'%')
and note like concat('%',#{note},'%')
</select>
使用resultMap映射结果集
inset元素
主键回填和自定义
自增主键回填xml配置
<insert id="insertRole" parameterType="role" useGeneratedKeys="true" keyProperty="id">
insert into t_role(role_name,note) values(#{roleName},#{note})
</insert>
自定义主键生成规则
最大id+2
<insert id="insert" parameterType="com.itcv.mybatis.demo.model.MatchRules" useGeneratedKeys="true" keyProperty="id" >
<selectKey keyProperty="id" resultType="int" order="BEFORE">
select if(max(id) is null,1, max(id)+2) as newId from match_rules
</selectKey>
insert into match_rules (id, rule_name, rule_type,
rule_state, sort_num, rule_field,
yn, create_time, update_time
)
values (#{id}, #{ruleName,jdbcType=VARCHAR}, #{ruleType,jdbcType=INTEGER},
#{ruleState,jdbcType=TINYINT}, #{sortNum,jdbcType=INTEGER}, #{ruleField,jdbcType=VARCHAR},
#{yn,jdbcType=TINYINT}, #{createTime,jdbcType=TIMESTAMP}, #{updateTime,jdbcType=TIMESTAMP}
)
</insert>
resultMap结果映射集
定义构造方法
当实体类中不存在没有参数的构造方法时,只存在 public MatchRules(Integer id,String ruleName) 时就需要配置这个结果集如
<resultMap id="test" type="com.itcv.mybatis.demo.model.MatchRules">
<constructor>
<idArg column="id" javaType="int"></idArg>
<arg column="rule_name" javaType="string"></arg>
</constructor>
</resultMap>
使用map存储结果集
<select id="findMapByParam" parameterType="string" resultMap="map">
select
<include refid="Base_Column_List" />
from match_rules where rule_name like concat('%',#{ruleName},'%')
</select>
使用POJO存储结果集
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.MatchRules" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="rule_name" property="ruleName" jdbcType="VARCHAR" />
<result column="rule_type" property="ruleType" jdbcType="INTEGER" />
<result column="rule_state" property="ruleState" jdbcType="TINYINT" />
<result column="sort_num" property="sortNum" jdbcType="INTEGER" />
<result column="rule_field" property="ruleField" jdbcType="VARCHAR" />
<result column="yn" property="yn" jdbcType="TINYINT" />
<result column="create_time" property="createTime" jdbcType="TIMESTAMP" />
<result column="update_time" property="updateTime" jdbcType="TIMESTAMP" />
</resultMap>
级联
- association,代表一对一关系
- collection,代表一对多关系
- discriminator,是鉴别器,可以根据特定的条件去关联不同结果集。
示例说明
学生表与学生证表是一对一的关系,学生表与课程成绩表是一对多的关系,学生有男有女,而健康项目也有所不一,所以男女的健康表也会有所不同,这些是根据学生的性别来决定的,而鉴别学生性别的就是鉴别器。
association 一对一级联
StudentSelfcardMapper.xml
<mapper namespace="com.itcv.mybatis.demo.dao.StudentSelfcardMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.StudentSelfcard" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="student_id" property="studentId" jdbcType="INTEGER" />
<result column="native_address" property="nativeAddress" jdbcType="VARCHAR" />
<result column="issue_date" property="issueDate" jdbcType="TIMESTAMP" />
<result column="selfcard_no" property="selfcardNo" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id, student_id, native_address, issue_date, selfcard_no
</sql>
<select id="selectByStudentId" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from student_selfcard
where student_id = #{studentId,jdbcType=INTEGER}
</select>
</mapper>
StudentMapper.xml
<mapper namespace="com.itcv.mybatis.demo.dao.StudentMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.Student" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="stu_name" property="stuName" jdbcType="VARCHAR" />
<result column="selfcard_no" property="selfcardNo" jdbcType="INTEGER" />
<result column="sex" property="sex" jdbcType="TINYINT" />
<result column="note" property="note" jdbcType="VARCHAR" />
<association property="studentSelfcard" column="id" select="com.itcv.mybatis.demo.dao.StudentSelfcardMapper.selectByStudentId"></association>
</resultMap>
<sql id="Base_Column_List" >
id, stu_name, selfcard_no, sex, note
</sql>
<select id="getStudent" parameterType="int" resultMap="BaseResultMap">
select <include refid="Base_Column_List"></include>
from student where id =#{id}
</select>
</mapper>
测试代码
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student stu = studentMapper.getStudent(1);
System.out.println(stu.getStudentSelfcard().getNativeAddress());
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
这样获取学生信息的时候,就可以获取学生证的信息了。
重点代码
<association property="studentSelfcard" column="id" select="com.itcv.mybatis.demo.dao.StudentSelfcardMapper.selectByStudentId"></association>
在getStudent返回结果集里边配置这个就可以进行关联了,现在只有一个id参数,多个参数用逗号分割。
collection 一对多级联
一个学生有多门课程,没门课程都有自己的成绩。学生与课程的级联是一对多,课程与成绩是一对一级联。
StudentLectureMapper.xml 学生课程成绩表
<mapper namespace="com.itcv.mybatis.demo.dao.StudentLectureMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.StudentLecture" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="student_id" property="studentId" jdbcType="INTEGER" />
<result column="lecture_id" property="lectureId" jdbcType="INTEGER" />
<result column="score" property="score" jdbcType="DECIMAL" />
<association property="lecture" column="id" select="com.itcv.mybatis.demo.dao.LectureMapper.selectByPrimaryKey"></association>
</resultMap>
<sql id="Base_Column_List" >
id, student_id, lecture_id, score
</sql>
<select id="selectStudentLectureByStuId" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from student_lecture
where student_id = #{id}
</select>
</mapper>
LectureMapper.xml 课程表
<mapper namespace="com.itcv.mybatis.demo.dao.LectureMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.Lecture" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="lecture_name" property="lectureName" jdbcType="VARCHAR" />
<result column="note" property="note" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id, lecture_name, note
</sql>
<select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
select
<include refid="Base_Column_List" />
from lecture
where id = #{id,jdbcType=INTEGER}
</select>
</mapper>
StudentMapper.xml 学生表
<mapper namespace="com.itcv.mybatis.demo.dao.StudentMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.Student" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="stu_name" property="stuName" jdbcType="VARCHAR" />
<result column="selfcard_no" property="selfcardNo" jdbcType="INTEGER" />
<result column="sex" property="sex" jdbcType="TINYINT" />
<result column="note" property="note" jdbcType="VARCHAR" />
<association property="studentSelfcard" column="id" select="com.itcv.mybatis.demo.dao.StudentSelfcardMapper.selectByStudentId"></association>
<collection property="studentLectureList" column="id" select="com.itcv.mybatis.demo.dao.StudentLectureMapper.selectStudentLectureByStuId"></collection>
</resultMap>
<sql id="Base_Column_List" >
id, stu_name, selfcard_no, sex, note
</sql>
<select id="getStudent" parameterType="int" resultMap="BaseResultMap">
select <include refid="Base_Column_List"></include>
from student where id =#{id}
</select>
</mapper>
测试代码
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student stu = studentMapper.getStudent(1);
System.out.println(stu.getStudentSelfcard().getNativeAddress());
List<StudentLecture> studentLectureList = stu.getStudentLectureList();
System.out.println(studentLectureList.size());
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
总结
配置一对多级联步骤
-
在resultMap中配置
<collection property="studentLectureList" column="id" select="com.itcv.mybatis.demo.dao.StudentLectureMapper.selectStudentLectureByStuId"></collection> </resultMap> -
在student类中添加 List studentLectureList 属性
-
在StudentLectureMapper接口中添加根据id查询课程成绩的接口
discriminator 鉴别器级联
根据不同的条件,关联不同的数据,比如根据性别去关联健康指标
StudentHealthFemaleMapper.xml
<mapper namespace="com.itcv.mybatis.demo.dao.StudentHealthFemaleMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.StudentHealthFemale" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="student_id" property="studentId" jdbcType="VARCHAR" />
<result column="check_date" property="checkDate" jdbcType="TIMESTAMP" />
<result column="heart" property="heart" jdbcType="VARCHAR" />
<result column="liver" property="liver" jdbcType="VARCHAR" />
<result column="kidney" property="kidney" jdbcType="VARCHAR" />
<result column="uterus" property="uterus" jdbcType="VARCHAR" />
</resultMap>
<sql id="Base_Column_List" >
id, student_id, check_date, heart, liver, kidney, uterus
</sql>
<select id="findStudentHealthFemaleByStuId" resultMap="BaseResultMap" parameterType="java.lang.Integer">
select <include refid="Base_Column_List"/>
from student_health_female
where student_id = #{studentId}
</select>
</mapper>
StudentMapper.xml
<mapper namespace="com.itcv.mybatis.demo.dao.StudentMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.Student" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="stu_name" property="stuName" jdbcType="VARCHAR" />
<result column="selfcard_no" property="selfcardNo" jdbcType="INTEGER" />
<result column="sex" property="sex" jdbcType="TINYINT" />
<result column="note" property="note" jdbcType="VARCHAR" />
<association property="studentSelfcard" column="id" select="com.itcv.mybatis.demo.dao.StudentSelfcardMapper.selectByStudentId"></association>
<collection property="studentLectureList" column="id" select="com.itcv.mybatis.demo.dao.StudentLectureMapper.selectStudentLectureByStuId"></collection>
<discriminator javaType="int" column="sex">
<case value="1" resultMap="femaleStudent"></case>
<case value="2" resultMap="maleStudent"></case>
</discriminator>
</resultMap>
<resultMap id="maleStudent" type="com.itcv.mybatis.demo.model.MaleStudent" extends="BaseResultMap">
<collection property="studentHealthMaleList" column="id" select="com.itcv.mybatis.demo.dao.StudentHealthMaleMapper.findStudentHealthMaleByStuId"></collection>
</resultMap>
<resultMap id="femaleStudent" type="com.itcv.mybatis.demo.model.FemaleStudent" extends="BaseResultMap">
<collection property="studentHealthFemaleList" column="id" select="com.itcv.mybatis.demo.dao.StudentHealthFemaleMapper.findStudentHealthFemaleByStuId"></collection>
</resultMap>
<select id="getStudent" parameterType="int" resultMap="BaseResultMap">
select <include refid="Base_Column_List"></include>
from student where id =#{id}
</select>
</mapper>
FemaleStudent
public class FemaleStudent extends Student {
private List<StudentHealthFemale> studentHealthFemaleList;
public List<StudentHealthFemale> getStudentHealthFemaleList() {
return studentHealthFemaleList;
}
public void setStudentHealthFemaleList(List<StudentHealthFemale> studentHealthFemaleList) {
this.studentHealthFemaleList = studentHealthFemaleList;
}
}
测试代码
public class DiscriminatorTest {
public static void main(String[] args) {
SqlSession sqlSession = null;
try {
sqlSession = SqlSessionFactoryUtil.openSqlSession();
StudentMapper studentMapper = sqlSession.getMapper(StudentMapper.class);
Student stu = studentMapper.getStudent(1);
String str = stu.getSex().toString().equals("1")?"男":"女";
System.out.println("姓名:"+stu.getStuName()+" 学号: "+stu.getSelfcardNo()+"性别: "+str);
System.out.println("籍贯:"+stu.getStudentSelfcard().getNativeAddress());
System.out.println("课程 : 成绩: ");
for(StudentLecture studentLecture :stu.getStudentLectureList()){
System.out.println(studentLecture.getLecture().getLectureName()+" "+studentLecture.getScore());
}
System.out.println("健康信息");
if(stu.getSex().toString().equals("1")){
FemaleStudent femaleStudent = (FemaleStudent) stu;
List<StudentHealthFemale> studentHealthFemaleList = femaleStudent.getStudentHealthFemaleList();
for (StudentHealthFemale female: studentHealthFemaleList) {
System.out.println(female.getUterus());
}
}
}catch (Exception e){
e.printStackTrace();
}finally {
sqlSession.close();
}
}
}
总结
mybatis配置鉴别器级联步骤
- 添加xml配置
<discriminator javaType="int" column="sex">
<case value="1" resultMap="femaleStudent"></case>
<case value="2" resultMap="maleStudent"></case>
</discriminator>
<resultMap id="femaleStudent" type="com.itcv.mybatis.demo.model.FemaleStudent" extends="BaseResultMap">
<collection property="studentHealthFemaleList" column="id" select="com.itcv.mybatis.demo.dao.StudentHealthFemaleMapper.findStudentHealthFemaleByStuId"></collection>
</resultMap>
- 新建java类 FemaleStudent extends Student
- 在StudentHealthFemaleMapper中添加查询方法
性能分析
级联的优势是能方便的获取数据,它的缺点是会取出我们不需要的数据,比如查询成绩时,会把健康信息带出来,有一个关联就需要多执行一次sql,这样就会造成sql执行过多,导致性能下降。
延迟加载
为了处理多执行sql的问题,MyBatis引入了延迟加载的功能。
全局配置延迟加载
mybatis-config.xml配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/> <!--开启延迟加载 单独配置这个是不生效的-->
<setting name="aggressiveLazyLoading" value="false"/> <!--默认true 按层级加载 否则按需加载 -->
<!--解释一下按层级加载 通俗的说就是有直接关系 举例: 学生与成绩是有直接关系的,会加载,课程跟学生是没有直接关系的,所以不会加载-->
</settings>
局部延迟加载
局部延迟加载的出现是因为全局配置不够灵活,比如有些属性需要延迟,有些不需要延迟,即时加载性能会提高。
mybatis-config.xml配置
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<!-- <setting name="aggressiveLazyLoading" value="false"/>-->
</settings>
StudentMapper.xml 需要设置即使加载的 添加 fetchType="eager" 默认值 延迟加载的设置 为lazy
<mapper namespace="com.itcv.mybatis.demo.dao.StudentMapper" >
<resultMap id="BaseResultMap" type="com.itcv.mybatis.demo.model.Student" >
<id column="id" property="id" jdbcType="INTEGER" />
<result column="stu_name" property="stuName" jdbcType="VARCHAR" />
<result column="selfcard_no" property="selfcardNo" jdbcType="INTEGER" />
<result column="sex" property="sex" jdbcType="TINYINT" />
<result column="note" property="note" jdbcType="VARCHAR" />
<association property="studentSelfcard" fetchType="lazy" column="id" select="com.itcv.mybatis.demo.dao.StudentSelfcardMapper.selectByStudentId"></association>
<collection property="studentLectureList" fetchType="eager" column="id" select="com.itcv.mybatis.demo.dao.StudentLectureMapper.selectStudentLectureByStuId"></collection>
<discriminator javaType="int" column="sex">
<case value="1" resultMap="femaleStudent"></case>
<case value="2" resultMap="maleStudent"></case>
</discriminator>
</resultMap>
缓存 cache
MyBatis默认情况下只开启一级缓存(一级缓存只对相对于同一个SqlSession而言)
一级缓存
所有参数和SQL完全一样的情况下,我们使用同一个SqlSession调用同一个Mapper的方法,往往只执行一次SQL,因为使用SqlSession第一次查询后,MyBatis会将其放在缓存中,如果没有声明要刷新,且缓存没超时的情况下,SqlSession都只会取出当前缓存的数据。
二级缓存
二级缓存在SqlSessionFactory层面上能够提供给各个SqlSession对象共享,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。
配置二级缓存
只需要在映射XML文件配置就可以开启缓存了,默认配置
<!--开启二级缓存-->
<cache/>
如果是这样配置的话,那么意味着:
- 映射语句文件中的所有select 语句将会被缓存。
- 映射语句文件中的所有insert、update和delete语句会刷新缓存。
- 缓存会使用默认的Least Recently Used(LRU,最近很少使用)算法来收回。
- 根据时间表,比如No Flush Interval,(CNFI,没有刷新间隔),缓存不会以任何时间顺序来刷新。
- 缓存会存储列表集合或对象(无论查询返回什么)的1024个引用。
- 缓存会被视为是read/write(可读/可写)的缓存,意味着检索对象不是共享的,而且可以安全地被调用者修改,不干扰其他调用者或线程所做的潜在修改。
修改默认配置
<!--开启二级缓存-->
<cache eviction="LRU" flushInterval="100000" size="1024" readOnly="true"/>
-
eviction:代表缓存回收策略,目前Mybatis提供以下策略。
- LRU,最近最少使用的,移除最长时间不用的对象
- FIFO,先进先出,按对象进入缓存的顺序来移除它们。
- SOFT,软引用,移除基于垃圾回收器状态和软引用规则的对象。
- WEAK,弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象。
-
flushInterval: 刷新间隔时间,单位为毫秒,这里配置的是100秒刷新,如果不配置的话,当SQL被执行的时候才会刷新缓存。
-
size: 引用数目,一个正整数,代表缓存最多可以存储多少个对象,不宜设置过大
-
readOnly: 只读,意味着缓存数据只能读取不能修改。默认值为false,不允许我们修改
自定义缓存
系统缓存是Mybatis应用机器上的本地缓存,但是在大型服务器上,会使用各类不同的缓存服务器,这个时候我们可以定制缓存。
动态sql
bind元素
<select id="getStudent" parameterType="int" resultMap="BaseResultMap">
<bind name="pattern" value="'%'+ id+'%'"/>
select <include refid="Base_Column_List"></include>
from student where id like #{pattern}
</select>
// ****** ****** ****** ****** ****** ****** 第二部分源码分析 ****** ****** ****** ****** ****** ****** ****** ****** ****** ***/
源码分析
MyBatis的运行分为两大部分,第一部分是读取配置文件缓存到Configuration对象,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程。
设计的技术难点简介
动态代理
JDK反射机制提供的代理
JDK提供的代理必须要提供接口
CGLIB代理
CGLIB不需要提供接口
start
public static void main(String[] args) {
try {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
MatchRulesMapper mapper = sqlSession.getMapper(MatchRulesMapper.class);
// ProxyUtils.generateClassFile(mapper.getClass(),"MatchRulesProxy");
Integer maxNum = mapper.maxSortNum();
System.out.println(maxNum);
} catch (IOException e) {
e.printStackTrace();
}
}
读取mybatis-config.xml配置文件,生成输入流
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactoryBuilder通过输入流构建SqlSessionFactory
西红柿炒鸡蛋需要的材料准备好了
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder(){}//没有属性
}
SqlSessionFactoryBuilder.build(inputStream)
public class SqlSessionFactoryBuilder {
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, null, null);
return build(parser.parse());
}
}
实例化XMLConfigBuilder
public class XMLConfigBuilder extends BaseBuilder {
private boolean parsed;
private final XPathParser parser;
private String environment;
private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
private XMLConfigBuilder(XPathParser parser, String environment = null, Properties props = null) {
//初始化configuration
super(new Configuration());
ErrorContext.instance().resource("SQL Mapper Configuration");//这个代码先忽略
this.configuration.setVariables(props);
this.parsed = false;
this.environment = environment;
this.parser = parser;
}
}
public class DefaultReflectorFactory implements ReflectorFactory {
private boolean classCacheEnabled = true;
private final ConcurrentMap<Class<?>, Reflector> reflectorMap = new ConcurrentHashMap<>();
}
实例化Configuration()
public class Configuration {
//属性太多了,这里没有全部列举
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
public Configuration() { //初始化Configuration
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
typeAliasRegistry.registerAlias("LRU", LruCache.class);
typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
languageRegistry.register(RawLanguageDriver.class);
}
}
}
添加到map中
public class TypeAliasRegistry {
private final Map<String, Class<?>> typeAliases = new HashMap<>();
public void registerAlias(String alias, Class<?> value) {
// issue #748
String key = alias.toLowerCase(Locale.ENGLISH);
if (typeAliases.containsKey(key) && typeAliases.get(key) != null && !typeAliases.get(key).equals(value)) {
throw new TypeException("The alias '" + alias + "' is already mapped to the value '" + typeAliases.get(key).getName() + "'.");
}
typeAliases.put(key, value);
}
}
XMLLanguageDriver,RawLanguageDriver实例化之后放到map里边
public class LanguageDriverRegistry{
private final Map<Class<? extends LanguageDriver>, LanguageDriver> LANGUAGE_DRIVER_MAP = new HashMap<>();
private Class<? extends LanguageDriver> defaultDriverClass;
public void register(Class<? extends LanguageDriver> cls) {
//判断类是否存放在map中,不存在则实例化然后放进去
MapUtil.computeIfAbsent(LANGUAGE_DRIVER_MAP, cls, k -> {
return k.getDeclaredConstructor().newInstance();
});
}
}
XMLLanguageDriver 是一个空对象 implements LanguageDriver;
RawLanguageDriver extends XMLLanguageDriver,RawLanguageDriver也是一个空对象;
给XMLConfigBuilder赋值
public abstract class BaseBuilder {
protected final Configuration configuration;
protected final TypeAliasRegistry typeAliasRegistry;
protected final TypeHandlerRegistry typeHandlerRegistry;
public BaseBuilder(Configuration configuration) {
this.configuration = configuration;
//别名记录 "_boolean" --> Boolean.class
this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
//别名记录
this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
}
XMLConfigBuilder.parse()
解析mybatis-config.xml,*Mapper.xml 文件
public Configuration parse() {
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
//解析配置文件并放到configuration中
private void parseConfiguration(XNode root) {
//解析 全局参数properties配置
propertiesElement(root.evalNode("properties"));
//获取settings配置属性
Properties settings = settingsAsProperties(root.evalNode("settings"));
//vfs
loadCustomVfs(settings);
//日志设置
loadCustomLogImpl(settings);
//typeAliases别名配置
typeAliasesElement(root.evalNode("typeAliases"));
//插件解析
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
//environments配置解析
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//解析mapper巴拉巴拉
mapperElement(root.evalNode("mappers"));
}
解析全局参数properties配置
把解析出来的properties放到 configuration.setVariables属性中,resource 和 url 同时配置会报错
<properties resource="jdbc.properties"></properties>
private void propertiesElement(XNode context) throws Exception {
if (context != null) {
Properties defaults = context.getChildrenAsProperties();
String resource = context.getStringAttribute("resource");
String url = context.getStringAttribute("url");
// resource 和 url 同时配置会报错
if (resource != null) {
defaults.putAll(Resources.getResourceAsProperties(resource));
} else if (url != null) {
defaults.putAll(Resources.getUrlAsProperties(url));
}
Properties vars = configuration.getVariables();
if (vars != null) {
defaults.putAll(vars);
}
parser.setVariables(defaults);
configuration.setVariables(defaults);
}
}
解析environments配置 environmentsElement()
配置如下:
<environments default="development">
<environment id="development">
<transactionManager type="JDBC">
<property name="autoCommit" value="false"/>
</transactionManager>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://192.168.0.131:3306/mybatis?serverTimezone=UTC"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
为configuration设置数据库环境
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
if (environment == null) {
environment = context.getStringAttribute("default");//"development" 默认环境
}
for (XNode child : context.getChildren()) { // <environment>
String id = child.getStringAttribute("id"); // development
if (isSpecifiedEnvironment(id)) { //判断参数enviroment 是否跟id一致
TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));//事务配置 返回一个事务工厂
DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));//数据源配置
DataSource dataSource = dsFactory.getDataSource();
Environment.Builder environmentBuilder = new Environment.Builder(id)
.transactionFactory(txFactory)
.dataSource(dataSource);
// configuration设置environment
configuration.setEnvironment(environmentBuilder.build());
break;
}
}
}
}
//实例化JdbcTransactionFactory并返回
private TransactionFactory transactionManagerElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type"); // type ="JDBC"
Properties props = context.getChildrenAsProperties();
//从configuration.typeAliasRegistry中获取JdbcTransactionFactory并进行实例化
TransactionFactory factory = (TransactionFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a TransactionFactory.");
}
// 实例化一个DataSourceFactory并返回
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type"); // type ="POOLED"
Properties props = context.getChildrenAsProperties();
// 实例化PooledDataSourceFactory
DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
factory.setProperties(props);
return factory;
}
throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}
}
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {
public PooledDataSourceFactory() {
this.dataSource = new PooledDataSource();
}
}
public class PooledDataSource implements DataSource {
private static final Log log = LogFactory.getLog(PooledDataSource.class);
private final PoolState state = new PoolState(this);
private final UnpooledDataSource dataSource;
// OPTIONAL CONFIGURATION FIELDS
protected int poolMaximumActiveConnections = 10;
protected int poolMaximumIdleConnections = 5;
protected int poolMaximumCheckoutTime = 20000;
protected int poolTimeToWait = 20000;
protected int poolMaximumLocalBadConnectionTolerance = 3;
protected String poolPingQuery = "NO PING QUERY SET";
protected boolean poolPingEnabled;
protected int poolPingConnectionsNotUsedFor;
private int expectedConnectionTypeCode;
public PooledDataSource() {
dataSource = new UnpooledDataSource();
}
}
public class UnpooledDataSource implements DataSource {
private ClassLoader driverClassLoader;
private Properties driverProperties;
private static Map<String, Driver> registeredDrivers = new ConcurrentHashMap<>();
private String driver;
private String url;
private String username;
private String password;
private Boolean autoCommit;
private Integer defaultTransactionIsolationLevel;
private Integer defaultNetworkTimeout;
static {
Enumeration<Driver> drivers = DriverManager.getDrivers();
while (drivers.hasMoreElements()) {
Driver driver = drivers.nextElement();
registeredDrivers.put(driver.getClass().getName(), driver);
}
}
public UnpooledDataSource() {
}
// 设置数据库连接
public void setProperties(Properties properties) {
Properties driverProperties = new Properties();
MetaObject metaDataSource = SystemMetaObject.forObject(dataSource);
for (Object key : properties.keySet()) {
String propertyName = (String) key;
if (propertyName.startsWith(DRIVER_PROPERTY_PREFIX)) {
String value = properties.getProperty(propertyName);
driverProperties.setProperty(propertyName.substring(DRIVER_PROPERTY_PREFIX_LENGTH), value);
} else if (metaDataSource.hasSetter(propertyName)) {
String value = (String) properties.get(propertyName);
Object convertedValue = convertValue(metaDataSource, propertyName, value);
metaDataSource.setValue(propertyName, convertedValue);
} else {
throw new DataSourceException("Unknown DataSource property: " + propertyName);
}
}
if (driverProperties.size() > 0) {
metaDataSource.setValue("driverProperties", driverProperties);
}
}
}
解析mappers配置 mapperElement()
<mappers>
<mapper resource="mapper/MatchRulesMapper.xml"/>
<mapper resource="mapper/StudentMapper.xml"/>
</mappers>
//读取MatchRulesMapper.xml文件放到configuration中
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {//遍历<mapper>
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} 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);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) { //读取文件
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(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.");
}
}
}
}
}
构建XMLMapperBuilder
public class XMLMapperBuilder extends BaseBuilder {
private final XPathParser parser;
private final MapperBuilderAssistant builderAssistant;
private final Map<String, XNode> sqlFragments;
private final String resource;
public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
//读取输入流生成docment
this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()),
configuration, resource, sqlFragments);
}
private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
super(configuration);// 同 2.1.1.2 赋值configuration
this.builderAssistant = new MapperBuilderAssistant(configuration, resource);
this.parser = parser;
this.sqlFragments = sqlFragments;
this.resource = resource;
}
}
public class MapperBuilderAssistant extends BaseBuilder {
private String currentNamespace;
private final String resource;
private Cache currentCache;
private boolean unresolvedCacheRef; // issue #676
public MapperBuilderAssistant(Configuration configuration, String resource) {
super(configuration);
ErrorContext.instance().resource(resource);
this.resource = resource;
}
}
XMLMapperBuilder.parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) { //判断resource是否已经加载过了
configurationElement(parser.evalNode("/mapper")); //获取节点mapper的所有数据解析
configuration.addLoadedResource(resource); //将resource 添加到configuration中Set<String> LoadedResource 中
bindMapperForNamespace();//将namespace 添加到loadedResource
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
- 获取mapper标签进行解析 configurationElement(parser.evalNode("/mapper"));
//解析mapper节点中的数据
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");// "com.itcv.mybatis.demo.dao.MatchRulesMapper"
if (namespace == null || namespace.isEmpty()) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache")); //是否启用二级缓存 构建了一个Cache类并放到configuration中
parameterMapElement(context.evalNodes("/mapper/parameterMap")); //查询参数 构建ParameterMap 放到configuration中
resultMapElements(context.evalNodes("/mapper/resultMap")); //结果集 解析resultMap放到configuration中
sqlElement(context.evalNodes("/mapper/sql"));// 解析sql 放到sqlFragments属性中
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);
}
}
1.1 解析cache标签
//cache解析
private void cacheElement(XNode context) {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL"); //是否自定义缓存 默认PERPETUAL
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); //PerpetualCache
String eviction = context.getStringAttribute("eviction", "LRU");//回收内存策略 默认LRU
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval"); //刷新间隔 单位ms
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
//构建了一个Cache类并放到configuration中
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
1.2 解析"select|insert|update|delete"标签
//"select|insert|update|delete" 解析这些标签 构建了 MappedStatement statement = statementBuilder.build();
//并且把statement 放到了configuration中 configuration.addMappedStatement(statement);
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//实例化并赋值
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
1.2.1 "select|insert|update|delete" 解析这些标签并生成statement
public class MapperBuilderAssistant extends BaseBuilder {
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
}
- XMLConfigBuilder.bindMapperForNamespace()
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
// ignore, bound type is not required
}
if (boundType != null && !configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
2.1 configuration.addMapper
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type)); //放入一个工厂代理类
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);//注解方式解析 这里我们没有用注解,先跳过
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
}
SqlSessionFactoryBuilder.build(configuration)
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
开启会话
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//初始化一个事务
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建一个执行器
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();
}
}
}
初始化事务
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
return new JdbcTransaction(ds, level, autoCommit);
}
public class JdbcTransaction implements Transaction {
private static final Log log = LogFactory.getLog(JdbcTransaction.class);
protected Connection connection;
protected DataSource dataSource;
protected TransactionIsolationLevel level;
protected boolean autoCommit;
public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
dataSource = ds;
level = desiredLevel;
autoCommit = desiredAutoCommit;
}
}
创建执行器
public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
}
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;
}
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
}
public abstract class BaseExecutor implements Executor {
private static final Log log = LogFactory.getLog(BaseExecutor.class);
protected Transaction transaction;
protected Executor wrapper;
protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
protected int queryStack;
private boolean closed;
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
}
返回默认会话
public class DefaultSqlSession implements SqlSession {
private final Configuration configuration;
private final Executor executor;
private final boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
this.configuration = configuration;
this.executor = executor;
this.dirty = false;
this.autoCommit = autoCommit;
}
}
从会话中获取Mapper接口
MatchRulesMapper mapper = sqlSession.getMapper(MatchRulesMapper.class);
通过动态代理生成被代理对象,返回代理对象
public class DefaultSqlSession implements SqlSession {
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
}
public class Configuration {
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
}
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//从map中拿出代理工厂
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);
}
}
}
代理对象工厂初始化动态代理类
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
//这是一个动态代理类
public class MapperProxy<T> implements InvocationHandler, Serializable {
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
}
执行方法
Integer maxNum = mapper.maxSortNum();
public class MapperProxy<T> implements InvocationHandler, Serializable {
//实际执行的方法
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 {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
}
返回一个MapperMethodInvoker
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
//判断method是否存在methodCache中,不存在则执行 m->{}块中的方法 m指 method
return MapUtil.computeIfAbsent(methodCache, method, m -> {
if (m.isDefault()) {
try {
if (privateLookupInMethod == null) {
return new DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
//首先实例化一个MapperMethod
return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
判断key是否存在map中,存在则返回value,不存在则添加function返回的结果
public static <K, V> V computeIfAbsent(Map<K, V> map, K key, Function<K, V> mappingFunction) {
V value = map.get(key);
if (value != null) {
return value;
}
return map.computeIfAbsent(key, mappingFunction::apply);
}
实例化一个MapperMethod
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
//拿到sql的类型是查询还是update...
this.command = new SqlCommand(config, mapperInterface, method);
//
this.method = new MethodSignature(config, mapperInterface, method);
}
}
实例化一个SqlCommand确定sql的类型
SqlCommandType是一个枚举类
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH
}
//SqlCommand是MapperMethod的一个内部类
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();
final Class<?> declaringClass = method.getDeclaringClass();
//从configuration的一个map中获取mappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
从configuration的一个map中获取mappedStatement
mappedStatements
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
实例化MethodSignature
确定返回类型
//也是MapperMethod的一个内部类
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {
//确认返回类型
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>) {
this.returnType = (Class<?>) resolvedReturnType;
} else if (resolvedReturnType instanceof ParameterizedType) {
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
} else {
this.returnType = method.getReturnType();
}
//是否返回void
this.returnsVoid = void.class.equals(this.returnType);
//是否返回集合
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.returnsOptional = Optional.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
}
实例化 PlainMethodInvoker
//MapperProxy的一个内部接口
interface MapperMethodInvoker {
Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
}
private static class PlainMethodInvoker implements MapperMethodInvoker {
private final MapperMethod mapperMethod;
//进行了一个简单的赋值
public PlainMethodInvoker(MapperMethod mapperMethod) {
super();
this.mapperMethod = mapperMethod;
}
}
执行invoke
private static class PlainMethodInvoker implements MapperMethodInvoker {
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
return mapperMethod.execute(sqlSession, args);
}
}
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 {
//解析参数放到一个map里边
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) {
final int paramCount = names.size();
if (args == null || paramCount == 0) {
return null;
} else if (!hasParamAnnotation && paramCount == 1) {
Object value = args[names.firstKey()];
return wrapToMapIfCollection(value, useActualParamName ? names.get(0) : null);
} else {
final Map<String, Object> param = new ParamMap<>();
int i = 0;
for (Map.Entry<Integer, String> entry : names.entrySet()) {
param.put(entry.getValue(), args[entry.getKey()]);
// add generic param names (param1, param2, ...)
final String genericParamName = GENERIC_NAME_PREFIX + (i + 1);
// ensure not to overwrite parameter named with @Param
if (!names.containsValue(genericParamName)) {
param.put(genericParamName, args[entry.getKey()]);
}
i++;
}
return param;
}
}
执行查询指令 sqlSession.selectOne
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} 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;
}
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler = null) {
try {
//从configuration的map中 获取mappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
从configuration的map中 获取mappedStatement
执行查询
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//拿到sql,参数
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
绑定sql,参数
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
public class BoundSql {
private final String sql;
private final List<ParameterMapping> parameterMappings;
private final Object parameterObject;
private final Map<String, Object> additionalParameters;
private final MetaObject metaParameters;
public BoundSql(Configuration configuration, String sql, List<ParameterMapping> parameterMappings, Object parameterObject) {
this.sql = sql;
this.parameterMappings = parameterMappings;
this.parameterObject = parameterObject;
this.additionalParameters = new HashMap<>();
this.metaParameters = configuration.newMetaObject(additionalParameters);
}
通过算法生成一个缓存key
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
public class CacheKey implements Cloneable, Serializable {
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;
private final int multiplier;
private int hashcode;
private long checksum;
private int count;
// 8/21/2017 - Sonarlint flags this as needing to be marked transient. While true if content is not serializable, this
// is not always true and thus should not be marked transient.
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLIER;
this.count = 0;
this.updateList = new ArrayList<>();
}
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
}
查询
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++;
//从缓存中判断是否存在缓存key
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;
}
- 从缓存中读取数据
private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
if (ms.getStatementType() == StatementType.CALLABLE) {
final Object cachedParameter = localOutputParameterCache.getObject(key);
if (cachedParameter != null && parameter != null) {
final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
final MetaObject metaParameter = configuration.newMetaObject(parameter);
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
if (parameterMapping.getMode() != ParameterMode.IN) {
final String parameterName = parameterMapping.getProperty();
final Object cachedValue = metaCachedParameter.getValue(parameterName);
metaParameter.setValue(parameterName, cachedValue);
}
}
}
}
}
- 从数据库中读取数据
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
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;
}
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
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);
}
}
2.1返回一个BaseStatementHandler
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;
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
2.2 构建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取数据库连接
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
public abstract class BaseStatementHandler implements StatementHandler {
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
}
instantiateStatement
public class PreparedStatementHandler extends BaseStatementHandler {
protected Statement instantiateStatement(Connection connection) throws SQLException {
//获取sql
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
}
connection.prepareStatement(sql);
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
2.3 执行sql,返回结果
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}