能够说出mybatis的核心组件
能够说出mybatis的配置解析流程
能够说出mybatis一次查询的源码流程
Mybatis源码解析
1.项目准备
准备Sql
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(100) DEFAULT NULL,
`birthday` datetime DEFAULT NULL,
`sex` varchar(10) DEFAULT NULL,
`address` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;
INSERT INTO `user` VALUES ('1', '刘备', '2019-11-26 15:31:09', '男', '蜀国');
INSERT INTO `user` VALUES ('2', '关羽', '2019-11-26 15:31:09', '男', '蜀国');
INSERT INTO `user` VALUES ('3', '孙尚香', '2019-11-05 15:32:00', '女', '吴国');
INSERT INTO `user` VALUES ('4', '杨贵妃', '2019-11-01 15:32:25', '女', '大唐');
pom依赖
<dependencies>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>RELEASE</version>
</dependency>
<!-- 淘淘项目 分页组件 -->
<dependency>
<groupId>com.github.miemiedev</groupId>
<artifactId>mybatis-paginator</artifactId>
<version>1.2.15</version>
</dependency>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>3.2.1</version>
</dependency>
</dependencies>
Properties配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis
jdbc.username=root
jdbc.password=root
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>
<!-- 配置properties
可以在标签内部配置连接数据库的信息。也可以通过属性引用外部配置文件信息
resource属性: 常用的
用于指定配置文件的位置,是按照类路径的写法来写,并且必须存在于类路径下。
url属性:
是要求按照Url的写法来写地址
URL:Uniform Resource Locator 统一资源定位符。它是可以唯一标识一个资源的位置。
它的写法:
http://localhost:8080/mybatisserver/demo1Servlet
协议 主机 端口 URI
URI:Uniform Resource Identifier 统一资源标识符。它是在应用中可以唯一定位一个资源的。
-->
<properties url="file:///E:\lession\itcast\work_plus\mybatis\mybatis_xml2\src\main\resources\jdbc.properties">
<!-- <property name="driver" value="com.mysql.jdbc.Driver"></property>
<property name="url" value="jdbc:mysql://localhost:3306/eesy_mybatis"></property>
<property name="username" value="root"></property>
<property name="password" value="1234"></property>-->
</properties>
<!--使用typeAliases配置别名,它只能配置domain中类的别名 -->
<typeAliases>
<!--typeAlias用于配置别名。type属性指定的是实体类全限定类名。alias属性指定别名,当指定了别名就再区分大小写
<typeAlias type="com.itheima.domain.User" alias="user"></typeAlias>-->
<!-- 用于指定要配置别名的包,当指定之后,该包下的实体类都会注册别名,并且类名就是别名,不再区分大小写-->
<package name="com.itcast.entity"></package>
</typeAliases>
<!-- 配置分页插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
<!--配置环境-->
<environments default="mysql">
<!-- 配置mysql的环境-->
<environment id="mysql">
<!-- 配置事务 -->
<transactionManager type="JDBC"></transactionManager>
<!--配置连接池-->
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</environment>
</environments>
<!-- 配置映射文件的位置 -->
<mappers>
<!--<mapper resource="com/itheima/dao/IUserDao.xml"></mapper>-->
<!-- package标签是用于指定dao接口所在的包,当指定了之后就不需要在写mapper以及resource或者class了 -->
<package name="com.itcast.mapper"></package>
</mappers>
</configuration>
User实体类
public class User{
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", username='" + username + '\'' +
", birthday=" + birthday +
", sex='" + sex + '\'' +
", address='" + address + '\'' +
'}';
}
}
IUserDao接口定义
public interface IUserDao {
List<User> findAll();
void update(User user);
void insert(User user);
void delete(Integer id);
User findById(Integer id);
}
IUserDao.xml定义
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itcast.mapper.IUserDao">
<!-- <resultMap id="BaseResultMap" type="com.itcast.domain.User">-->
<resultMap id="userMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
</resultMap>
<select id="findAll" resultMap="userMap" >
select * from USER
</select>
<select id="findById" resultMap="userMap">
select * from USER
<where>
id = #{id}
</where>
</select>
<update id="update" parameterType="User" >
update USER SET
username=#{username},
address=#{address},
birthday=#{birthday},
sex=#{sex},
address=#{address}
<where>
id=#{id}
</where>
</update>
<insert id="insert" parameterType="User" >
INSERT INTO USER
(id,username,birthday,sex,address)
VALUES
(null,#{username},#{birthday},#{sex},#{address})
</insert>
<delete id="delete" parameterType="int">
delete FROM USER where id=#{id}
</delete>
</mapper>
2.Mybatis核心组件
2.1 代码演示
案例
/**
* 测试查询全部账户列表数据
*/
@Test
public void findAllAccountTest() throws Exception{
// 1.加载mybatis框架主配置文件sqlMapConfig.xml
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2.读取解析配置文件内容,获取框架核心对象SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
// 3.获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4.获取接口代理对象
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
// 5.执行数据库操作
List<Account> list = accountDao.findAllAccounts();
for (Account ac:list){
System.out.println(ac);
}
// 6.释放资源
sqlSession.close();
}
2.2 核心组件演示
| 序号 | 组件名称 | 组件描述 | | —- | —- | —- |
| 1 | Configuration | 用于封装、维护mybatis框架的所有配置信息 |
| 2 | MappedStatement | 每个MappedStatement,封装维护了一个
| 3 | SqlSessionFactoryBuilder | 构建器,用于解析封装主配置文件内容,构建SqlSessionFactory核心对象 |
| 4 | SqlSessionFactory | 工厂类接口,通过工厂方法openSession创建SqlSession对象 |
| 5 | SqlSession | mybatis框架的顶层API,表示和数据库交互的会话,提供了完成数据库增删改查操作的接口方法 |
| 6 | Executor | mybatis框架执行器,是框架的调度核心,负责sql语句的生成和查询缓存维护 |
| 7 | StatementHandler | 封装jdbc Statement操作,比如设置参数、将Statement结果集转换成List |
| 8 | ParameterHandler | 将用户传递的参数,转换成jdbc Statement 所需要的参数 |
| 9 | SqlSource | 根据用户传递的parameterObject,动态生成sql语句,将信息封装到BoundSql对象中 |
| 10 | BoundSql | 动态生成的sql语句、和参数信息 |
| 11 | ResultSetHandler | 将jdbc返回的ResultSet结果集对象,进行结果集封装,转换成List类型的集合 |
| 12 | TypeHandler | 类型处理器,完成java数据类型和jdbc数据类型之间的映射和转换 |
#mybatis框架运行流程:
【1】.加载配置文件:
主配置文件:sqlMapConfig.xml。主配置文件用于配置框架的运行时环境
sql语句映射文件:Account.xml、AccountDao.xml。sql语句映射文件用于描述要执行的数据库操作
【2】.读取解析配置文件内容:
将配置文件内容,封装到Configuration对象中
根据主配置文件内容,获取框架的核心对象SqlSessionFactory
【3】.获取SqlSession对象:
通过SqlSessionFactory对象,获取到操作数据库的会话对象SqlSession
【4】.通过SqlSession对象,执行数据库操作
SqlSession直接通过相关API方法,执行数据库操作
SqlSession获取mapper接口代理对象,执行数据库操作(注意:底层还是SqlSession调用API方法)
【5】.通过Executor执行器,执行数据库操作
【6】.通过MappedStatement语句对象,完成数据库操作:
根据传递进来的Statement Id、和参数,动态组装成完整的sql语句,发送到数据库执行
将执行的结果,封装到结果集返回
3.mybatis运行原理(源码)
3.1 解析封装配置文件
#1.在mybatis框架中,配置文件主要有两类:
1.1.主配置文件:sqlMapConfig.xml
1.2.接口映射文件:mapper.xml,mapper1.xml......
#2.在使用mybatis框架中,引导启动加载配置文件信息代码如下:
// 1.加载mybatis框架主配置文件sqlMapConfig.xml
InputStream inputStream =
Resources.getResourceAsStream("sqlMapConfig.xml");
// 2.读取解析配置文件内容,获取框架核心对象SqlSessionFactory
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = builder.build(inputStream);
#3.在mybatis框架中,解析封装配置文件信息,提供了如下API:
SqlSessionFactoryBuilder:
构建器设计模式应用,用于构建SqlSessionFactory对象。该对象作用于建议是方法级别(局部变量),一旦创建好SqlSessionFactory,即不再需要。
Configuration:
配置信息对象,根据主配置文件sqlMapConfig.xml配置内容,进行封装。包括:属性资源文件、别名、数据源、mapper接口映射信息
MappedStatement:
每个MappedStatement,封装维护了一个<select|update|delete|insert>节点配置信息
BoundSql:
封装动态生成的sql语句、和参数信息
SqlSessionFactoryBuilder
#构建器设计模式应用,用于构建SqlSessionFactory对象。该对象作用于建议是方法级别(局部变量),一旦创建好SqlSessionFactory,即不再需要
图一:
图二:
图三:
Configuration
#配置信息对象,根据主配置文件sqlMapConfig.xml配置内容,进行封装。包括:属性资源文件、别名、数据源、mapper接口映射信息
源代码:
public Configuration() {
this.safeResultHandlerEnabled = true;
this.multipleResultSetsEnabled = true;
this.useColumnLabel = true;
this.cacheEnabled = true;//启用缓存
this.useActualParamName = true;
this.localCacheScope = LocalCacheScope.SESSION;
this.jdbcTypeForNull = JdbcType.OTHER;
this.lazyLoadTriggerMethods = new HashSet(Arrays.asList("equals", "clone", "hashCode", "toString"));// 触发延迟加载的方法
this.defaultExecutorType = ExecutorType.SIMPLE;//默认执行器
this.autoMappingBehavior = AutoMappingBehavior.PARTIAL;// 自动映射列到字段或属性策略
this.autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;// 未知列的处理策略
this.variables = new Properties();
this.reflectorFactory = new DefaultReflectorFactory();
this.objectFactory = new DefaultObjectFactory();// 对象工厂
this.objectWrapperFactory = new DefaultObjectWrapperFactory();
this.lazyLoadingEnabled = false;//延迟加载
this.proxyFactory = new JavassistProxyFactory();
this.mapperRegistry = new MapperRegistry(this);// mapper接口注册器
this.interceptorChain = new InterceptorChain();// 插件链
this.typeHandlerRegistry = new TypeHandlerRegistry();// 类型处理器注册器
this.typeAliasRegistry = new TypeAliasRegistry();// 别名注册器
this.languageRegistry = new LanguageDriverRegistry();
this.mappedStatements = (new Configuration.StrictMap("Mapped Statements collection")).conflictMessageProducer((savedValue, targetValue) -> {
return ". please check " + savedValue.getResource() + " and " + targetValue.getResource();
});// mapper映射文件中<select|insert|update|delete>标签配置
this.caches = new Configuration.StrictMap("Caches collection");// 缓存集合
this.resultMaps = new Configuration.StrictMap("Result Maps collection");// ResultMap集合
this.parameterMaps = new Configuration.StrictMap("Parameter Maps collection");// 参数集合
this.keyGenerators = new Configuration.StrictMap("Key Generators collection");
this.loadedResources = new HashSet();
this.sqlFragments = new Configuration.StrictMap("XML fragments parsed from previous mappers");// sql片段
this.incompleteStatements = new LinkedList();
this.incompleteCacheRefs = new LinkedList();
this.incompleteResultMaps = new LinkedList();
this.incompleteMethods = new LinkedList();
this.cacheRefMap = new HashMap();
this.typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
this.typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
this.typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);
this.typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
this.typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
this.typeAliasRegistry.registerAlias("LRU", LruCache.class);
this.typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
this.typeAliasRegistry.registerAlias("WEAK", WeakCache.class);
this.typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);
this.typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
this.typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);
this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
this.typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
this.typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
this.typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
this.typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);
this.typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
this.typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);
this.languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
this.languageRegistry.register(RawLanguageDriver.class);
}
MappedStatement
#每个MappedStatement,封装维护了一个<select|update|delete|insert>节点
小面试题:
id的组成方式
同一个mapper文件中,可不可以出现重载方法
不同mapper文件中,可不可以出现重载方法
图一:
BoundSql
#动态生成的sql语句、和参数信息
图一:
3.2一次查询过程源码分析
第一步:获取SqlSession
【1】/* 应用入口代码:MybatisDemo*/
// 3.获取SqlSession对象
SqlSession sqlSession = sqlSessionFactory.openSession();
---------------------------------------------------------------------------
【2】/*工厂接口实现类:DefaultSqlSessionFactory*/
// 第一步:获取一个操作数据库会话SqlSession对象
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
---------------------------------------------------------------------------
【3】// 第二步:获取SqlSession对象详细过程
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
DefaultSqlSession var8;
try {
Environment environment = this.configuration.getEnvironment();
TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 重点 创建执行器 执行器的分类
//<!--取值范围 SIMPLE, REUSE, BATCH -->
//<setting name="defaultExecutorType" value="SIMPLE"/>
Executor executor = this.configuration.newExecutor(tx, execType);
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
} catch (Exception var12) {
this.closeTransaction(tx);
throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12);
} finally {
ErrorContext.instance().reset();
}
return var8;
}
Executor(执行器)接口,
主要存在于SqlSession实例中,用于处理和数据库的交互。
有两个实现类:BaseExecutor 和 CachingExecutor
有三个继承BatchExecutor(重用语句并执行批量更新),ReuseExecutor(重用预处理语句prepared statements),SimpleExecutor(普通的执行器)。以上三个就是主要的Executor。Mybatis在Executor的设计上面使用了装饰者模式,我们可以用CachingExecutor来装饰前面的三个执行器目的就是用来实现缓存。
图一:
图二:
图三:
第二步:获取Mapper接口代理对象
【1】/*应用入口代码:*/
// 4.获取接口代理对象
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
【2】/*DefaultSqlSession*/
// 获取mapper接口代理对象
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
【3】/*Configuration*/
// 获取mapper接口代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
【4】/*MapperRegistry*/
// 通过动态代理技术,创建mapper接口代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
图一:
图二:
图三:
图四:
图五:
第三步:查询用户列表数据
【1】/*应用程序执行入口*/
// 5.执行数据库操作
List<Account> list = accountDao.findAllAccounts();
【2】/*MapperProxy,实现了InvocationHandler接口*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
if (this.isDefaultMethod(method)) {
return this.invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
// mapper接口代理方法对象
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
【3】/*MapperMethod*/
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
switch(this.command.getType()) {
case INSERT:// 添加,对应<insert>标签
param = this.method.convertArgsToSqlCommandParam(args);
// 可以看到,mapper接口代理实现方式,最终底层也是SqlSession.insert|update|select|delete方式执行
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:// 更新,对应<update>标签
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:// 删除,对应<delete>标签
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:// 查询,对应<select>标签
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
【4】/*BaseExecutor*/
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 (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
if (this.queryStack == 0 && ms.isFlushCacheRequired()) {
this.clearLocalCache();
}
List list;
try {
++this.queryStack;
// 优先从缓存取数据
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 如果缓存数据为空,即没有从缓存中取到数据,则从数据库查询数据
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
if (this.queryStack == 0) {
Iterator var8 = this.deferredLoads.iterator();
while(var8.hasNext()) {
BaseExecutor.DeferredLoad deferredLoad = (BaseExecutor.DeferredLoad)var8.next();
deferredLoad.load();
}
this.deferredLoads.clear();
if (this.configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
this.clearLocalCache();
}
}
return list;
}
}
【5】/*BaseExecutor*/
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
// 从数据库中查询获取数据
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
this.localCache.removeObject(key);
}
// 将数据放入本地缓存
this.localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
this.localOutputParameterCache.putObject(key, parameter);
}
return list;
}
【6】/*SimpleExecutor*/
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
// 获取StatementHandler
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 获取jdbc Statement对象
stmt = this.prepareStatement(handler, ms.getStatementLog());
// 执行查询,封装结果集
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}
【7】/*PreparedStatementHandler*/
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
【8】/*DefaultResultSetHandler*/
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(this.mappedStatement.getId());
List<Object> multipleResults = new ArrayList();
int resultSetCount = 0;
ResultSetWrapper rsw = this.getFirstResultSet(stmt);
List<ResultMap> resultMaps = this.mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
this.validateResultMapsCount(rsw, resultMapCount);
// ResultMap结果集映射
while(rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = (ResultMap)resultMaps.get(resultSetCount);
this.handleResultSet(rsw, resultMap, multipleResults, (ResultMapping)null);
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
// ResultSet结果集映射
String[] resultSets = this.mappedStatement.getResultSets();
if (resultSets != null) {
while(rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = (ResultMapping)this.nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = this.configuration.getResultMap(nestedResultMapId);
this.handleResultSet(rsw, resultMap, (List)null, parentMapping);
}
rsw = this.getNextResultSet(stmt);
this.cleanUpAfterHandlingResultSet();
++resultSetCount;
}
}
return this.collapseSingleResultList(multipleResults);
}
【9】/*DefaultResultSetHandler*/
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
this.handleRowValues(rsw, resultMap, (ResultHandler)null, RowBounds.DEFAULT, parentMapping);
} else if (this.resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(this.objectFactory);
this.handleRowValues(rsw, resultMap, defaultResultHandler, this.rowBounds, (ResultMapping)null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
this.handleRowValues(rsw, resultMap, this.resultHandler, this.rowBounds, (ResultMapping)null);
}
} finally {
this.closeResultSet(rsw.getResultSet());
}
}
图一:
图二:
图三:
图四:
图五:
图六:
图七:
图八:
图九:
图十:
图十一:
图十二:
流程小结:
-- 代码: SqlSession sqlSession = sqlSessionFactory.openSession();
源码:通过【DefaultSqlSessionFactory】的openSession方法获取SqlSession对象
1. 获取环境变量
2. 构建事务处理器
3. 构建Executor执行器
4. 设置是否自动提交事务
5. 根据上面内容构建SqlSession对象【DefaultSqlSession】
-- 代码:IUserDao mapper = sqlSession.getMapper(IUserDao.class);
源码:【DefaultSqlSession】的getMapper方法被调用
内部直接调用 configuration 的getMapper方法
configuration使用jdk动态代理,为IUserDao的接口创建了代理对象。
代理对象的处理在MapperProxy类中
-- 代码:List<User> all = mapper.findAll();
源码:实际上触发了代理类【MapperProxy】中invoke方法的调用
invoke方法中获取mapperMethod对象,这个对象记录的信息:
1. 本次查询的ID -> 类的全限定类名+方法名 如:com.itcast.mapper.IUserDao.findAll
2. 本次查询的类型 -> select insert update delete
【MapperMethod】的execute方法被调用
根据查询的类型,触发sqlSession的增删改查方法的执行
如:findAll方法 类型为select 触发 sqlSession.<E>selectList
【DefaultSqlSession】 的selectList方法被调用
根据查询的ID 在configuration的mappedStatements map集合中查询对应的MappedStatment对象
将查询到的ms作为参数,调用SqlSession的内部执行器对象Executor去执行
【CachingExecutor】的query方法被调用
根据ms对象获取对应的 BoundSql
查询缓存,缓存不存在调用【BaseExecutor】的query方法去数据库查询
【BaseExecutor】的queryFromDatabase方法被调用
对本次查询进行缓存处理
调用配置的Executor继续执行
【SimpleExecutor】的doQuery方法被调用
根据ms,boundSql等参数创建【StatementHandler】的对象
通过【StatementHandler】调用jdbc进行查询
通过【ResultHandler】进行结果封装,返回集合
Mybatis面试热点
Mybatis
mybatis的动态sql
MyBatis的一个强大特性之一通常是它的动态SQL能力。如果你有使用JDBC或其他相似框架的经验,你就明白条件串联SQL字符串在一起是多么地痛苦,确保不能忘了空格或者在列表的最后的省略逗号,动态SQL可以彻底处理这种痛苦。
通常使用动态SQL不可能是独立的一部分,MyBatis当然使用一种强大的动态SQL语言来改进这种情形,这种语言可以被用在任意映射的SQL语句中。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.itcast.dao.IUserMapper">
<!-- <cache />-->
<!-- <resultMap id="BaseResultMap" type="com.itcast.domain.User">-->
<resultMap id="userMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<collection property="list" ofType="com.itcast.domain.Account" fetchType="lazy" column="id" select="com.itcast.dao.IAccountMapper.findByUserId">
<id column="id" property="id"></id>
<result column="uid" property="uid"></result>
<result column="money" property="money"></result>
</collection>
</resultMap>
<select id="findById" resultMap="userMap">
<include refid="selectSql"></include>
<where>
id = #{id}
</where>
</select>
<select id="findAll" resultMap="userMap">
<include refid="selectSql"></include>
</select>
<!-- 使用动态标签 实现模糊查询 -->
<select id="findByCondition" parameterType="User" resultMap="userMap">
<include refid="selectSql"></include>
<where>
<if test="username !=null and username != ''">
AND username like #{username}
</if>
<if test="address !=null and address != ''">
AND address like #{address}
</if>
</where>
</select>
<update id="update" parameterType="User" >
update user SET
username=#{username},
address=#{address},
birthday=#{birthday},
sex=#{sex},
address=#{address}
<where>
id=#{id}
</where>
</update>
<insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user
(id,username,birthday,sex,address)
VALUES
(null,#{username},#{birthday},#{sex},#{address})
</insert>
<delete id="delete" parameterType="int">
delete FROM user where id=#{id}
</delete>
<insert id="insertBatchs">
insert INTO user
(username,birthday,sex,address)
VALUES
<foreach collection="list" item="u" separator=",">
(#{u.username},#{u.birthday},#{u.sex},#{u.address})
</foreach>
</insert>
<sql id="selectSql">
select * from user
</sql>
</mapper>
mybatis的多表查询
多对一、一对一
1对1,多对1 都是实体类里面关联另一个实体类
public class Account {
private Integer id;
private Integer uid;
private double money;
// 每个账户都关联一个User
User user;
}
在xml中定义映射关系, 使用association代表关联一个实体类, property映射的属性名,javaType该属性的实体类类型,fetchType是否延迟加载,column用于执行select方法所传递的参数,select需要执行的mapper方法的全限定名称
<resultMap id="BaseResultMap" type="com.itcast.domain.Account">
<id column="id" property="id"/>
<result column="uid" property="uid"/>
<result column="money" property="money"/>
<!-- account中 有一个属性为实体类user 以及user对应的映射 -->
<association property="user" javaType="User" fetchType="eager" column="uid" select="com.itcast.dao.IUserMapper.findById">
<id column="id" property="id"></id>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<result column="username" property="username"></result>
</association>
</resultMap>
一对多,多对多
对多,实体类中会有一个集合属性
public class User {
private Integer id;
private String username;
private Date birthday;
private String sex;
private String address;
// 对多,实体类中会有一个集合属性
private List<Account> list;
}
在xml中定义映射关系,因为是一个集合 所以使用collection标签,ofType代表集合中实体类的类型,其他属性和上面对一的情况一致
<resultMap id="userMap" type="User">
<id column="id" property="id"/>
<result column="username" property="username"/>
<result column="birthday" property="birthday"/>
<result column="address" property="address"/>
<result column="sex" property="sex"/>
<collection property="list" ofType="Account" column="id" select="com.itcast.dao.IAccountMapper.findByUserId">
<id column="id" property="id"></id>
<result column="uid" property="uid"></result>
<result column="money" property="money"></result>
</collection>
</resultMap>
延迟加载的问题
要使用延迟加载,需要在配置文件中开启延迟加载开关
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
在关联查询中,通过fetchType设置是否延迟加载, lazy表示延迟加载 eager表示立即加载
<association property="user" javaType="User" fetchType="lazy" column="uid" select="com.itcast.dao.IUserMapper.findById">
<id column="id" property="id"></id>
<result column="sex" property="sex"></result>
<result column="address" property="address"></result>
<result column="birthday" property="birthday"></result>
<result column="username" property="username"></result>
</association>
<collection property="list" ofType="Account" fetchType="lazy" column="id" select="com.itcast.dao.IAccountMapper.findByUserId">
<id column="id" property="id"></id>
<result column="uid" property="uid"></result>
<result column="money" property="money"></result>
</collection>
mybatis的缓存机制
一级缓存
一级缓存的范围是同一个SqlSession对象,当我们使用SqlSession对象进行查询时mybatis会帮我们把查询的数据存入到内存中,当我们在这个SqlSession中再一次执行同样的查询操作时,我们就可以直接去缓存中获取数据。
一级缓存是默认开启的
一级缓存的生命周期
a. MyBatis在开启一个数据库会话时,会 创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象;当会话结束时,SqlSession对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉。
b. 如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用;
c. 如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用;
d.SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用;
二级缓存
二级缓存是mapper级别的缓存,多个SqlSession共享,其作用域是mapper的同一个namespace,不同的SqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
二级缓存的配置
MyBatis二级缓存的划分
MyBatis并不是简单地对整个二级缓存分配整个Cache缓存对象,而是将缓存划分的更细,即是Mapper级别的,即每一个Mapper都可以拥有一个Cache对象,具体如下:
a.为每一个Mapper分配一个Cache缓存对象(使用节点配置);
b.多个Mapper共用一个Cache缓存对象(使用节点配置);
mybatis的二级缓存配置
MyBatis支持二级缓存的总开关:全局配置变量参数 cacheEnabled=true
该select语句所在的Mapper,配置了 或节点,并且有效
该select语句的参数 useCache=true
一级缓存和二级缓存的使用顺序
如果你的MyBatis使用了二级缓存,并且你的Mapper和select语句也配置使用了二级缓存,那么在执行select查询的时候,MyBatis会先从二级缓存中取输入,其次才是一级缓存,即MyBatis查询数据的顺序是:
二级缓存 ———> 一级缓存 ——> 数据库
二级缓存的实现
MyBatis对二级缓存的设计非常灵活,它自己内部实现了一系列的Cache缓存实现类,并提供了各种缓存刷新策略如LRU,FIFO等等;另外,MyBatis还允许用户自定义Cache接口实现,用户是需要实现org.apache.ibatis.cache.Cache接口,然后将Cache实现类配置在节点的type属性上即可;除此之外,MyBatis还支持跟第三方内存缓存库如Memecached的集成
总之,使用MyBatis的二级缓存有三个选择:
1.MyBatis自身提供的缓存实现;
2.用户自定义的Cache接口实现;
3.跟第三方内存缓存库的集成;
mybatis的批量操作
1.方式1, 通过mybatis的动态标签,已批量新增为例 比较常用
<!-- 批量新增-->
<insert id="batchSave" parameterType="java.util.List">
INSERT INTO lp_user_test_batch
(
id,
user_id,
user_name
)
VALUES
<foreach collection="list" item="item" index="index" separator=",">
(
#{item.id,jdbcType=BIGINT},
#{item.userId,jdbcType=VARCHAR},
#{item.userName,jdbcType=VARCHAR}
)
</foreach>
</insert>
2.方式2,通过mybatis的批处理特性,批量提交 ,比方式一略慢一些
定义一条sql语句
<!-- 批量新增-->
<insert id="batchSave" parameterType="java.util.List">
INSERT INTO lp_user_test_batch
(
id,
user_id,
user_name
)
VALUES
(
#{item.id,jdbcType=BIGINT},
#{item.userId,jdbcType=VARCHAR},
#{item.userName,jdbcType=VARCHAR}
)
</insert>
利用 MyBatis 批处理特性,批量提交(ExecutorType.BATCH)
SqlSession
Executor
simple (默认)
JDBC:
PreparedStatement pstat = new sql
reuse
做缓存处理:
PreparedStatement = new sql
batch
addBatch ()
/**
* 利用 MyBatis 批处理特性,批量提交
*/
public void batchInsert(List<UserTestBatchDO> testBatchDAOList) {
//集合非空
if (CollectionUtils.isEmpty(testBatchDAOList)) {
return;
}
//批处理方式 SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
//获得对应的Mapper
UserTestBatchDOMapper userTestBatchDOMapper = sqlSession.getMapper(UserTestBatchDOMapper.class);
try {
for (UserTestBatchDO testBatchDO : testBatchDAOList) {
userTestBatchDOMapper.insert(testBatchDO);
}
//统一提交
sqlSession.commit();
} catch (Exception e) {
//没有提交的数据可以回滚
sqlSession.rollback();
} finally {
//关闭 sqlSession
sqlSession.close();
}
}
方式三,方便单条控制事务, 但非常耗时,每循环一次都需要与数据库做一次交互
java程序循环调用单条修改语句
执行方式:一条sql ,程序循环执行``` for (UserTestBatchDO userTestBatch : testBatchDAOList) { userTestBatchDOMapper.updateByUserId(userTestBatch); }
```