1.我们先看jdbc的使用
pom文件引入mysql驱动
<!-- 避免生成大量 setter getter方法 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.25</version>
</dependency>
<!-- 为了方便和javaBean的转换 此处引入fastjson -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
新建测试类JdbcMain 主要代码如下:
public class JdbcMain {
public static void main(String[] args) throws SQLException {
//登录名
String uname ="root";
//密码
String password ="root123";
//数据库地址
String url ="jdbc:mysql://xxxxx:3306/xxx_production?useUnicode=true&characterEncoding=utf8";
//执行的sql语句
String sql ="select * from exam_user where name =?";
//①通过DriverManager创建Connection对象的实例
Connection connection = DriverManager.getConnection(url,uname,password);
//②创建Statement对象 用于执行sql语句
PreparedStatement statement = connection.prepareStatement(sql);
//③装备sql
statement.setString(1,"杨智");
//④执行查询语句
ResultSet resultSet = statement.executeQuery();
ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
int resCount = resultSetMetaData.getColumnCount();
Map map = new HashMap();
//⑤ 遍历结果集resultSet可以看做n条数据的结果集 next方法可以指向第n行
while (resultSet.next())
{
//依次遍历各个列
for (int i=0;i<resCount;i++)
{
String columnName =resultSetMetaData.getColumnName(i+1);
map.put(columnName,resultSet.getString(columnName));
}
ExamUser examUser = JSONObject.parseObject(JSONObject.toJSONString(map),ExamUser.class);
System.out.print(examUser.toString());
}
resultSet.close();
statement.close();
connection.close();
}
ExamUser类:
@Data
public class ExamUser {
private Long id;
private String name;
private String password;
private Long type;
private String login_name;
private Date create_time;
}
回忆大学时,把自己的代码贴给已工作的人看,当他看到了大量的原生态的jdbc代码,对我说了一句,这样会写死人的,的确,原生的jdbc现在来看有很多的缺陷,比如
①sql和java代码相互耦合 遇到稍微复杂的业务逻辑将会是维护的灾难
②没有用到连接池 大量的关闭和创建数据库连接 浪费资源
③结果集手动序列化 十分不便
此时我们需要转向帮我们做了很多事的mybaits 我们再看看mybaits的使用
2.mybatis的使用
先上一个项目目录 然后是对项目目录的逐个代码细节
pom文件:
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
image.png
以上为目录结构
Users中的代码:
public class Users {
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Users{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
UserMapper中的代码:
<?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.test.dao.UserDao">
<select id="getUserById" resultType="com.test.enity.Users">
select * from users where id = #{id}
</select>
</mapper>
MybatisMain中的代码:
public class MybatisMain {
public static void main(String[] args)
{
//获取配置文件test-mybatis 并将其转换为输入流
InputStream inputStream = MainApp.class.getResourceAsStream("/mybatis/test-mybatis");
//初始化SqlSessionFactory
SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
//初始化 SqlSession
SqlSession session =sqlSessionFactory.openSession();
//反向代理获得UserDao 反向代理的过程中将 UserDao和 UserMapper.xml中的查询方法相关联
UserDao userDao = session.getMapper(UserDao.class);
Users users = userDao.getUserById(17L);
System.out.print(users.toString());
}
}
test-mybaits中的代码:
<?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://mysqlserver.com:3306/xxx_production?characterEncoding=UTF-8"/>
<property name="username" value="root"/>
<property name="password" value="root123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/test/mapper/UserMapper.xml"/>
</mappers>
</configuration>
大体来说 将访问数据库的方式 分成了3个部分
1.创建访问数据库的接口UserDao
2.创建Mapper 并关联接口
<mapper namespace="com.test.dao.UserDao">
</mapper>
3.封装jdbc(MybatisMain里的主方法)
4.序列化对象并返回
3.mybatis源码分析
这里我们思考一下,mybaits都干了什么事情
首先 将sql语句、配置信息与java类进行分离 这样解决了jdbc的第一个问题 sql和java混在一起
下面自然产生出了3个疑问:
1.mybaits怎么用到的配置文件
2.mybaits怎么将接口和mapper整合在一起 调用时仿佛自己动态创建了UserDao的实现类
3.mybaits怎么将结果集转换为我们用到的实体类(这个问题以后再说- -)
4.既然mybaits封装了JDBC,那么和JDBC的代码是如何对应的
带着这三个疑问 我们往下看:
在主方法调用中我们看不见关于数据库链接的配置信息,第一步肯定是要加载这些配置文件,这就用到了
InputStream inputStream = MainApp.class.getResourceAsStream("/mybatis/test-mybatis");
SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
SqlSessionFactory意思是创建SqlSession的工厂 我们先看看这个工厂的结构
public interface SqlSessionFactory {
SqlSession openSession();
}
为了能整体把握 我们先简易观察这是个接口 找到其实现类(因为主方法中的openSession没有任何参数 所以我们只关心没有任何参数的那个实现 以下贴源码过程大都略过了大量代码 只保留一些关键信息以便我们学习)
public class DefaultSqlSessionFactory implements SqlSessionFactory {
private final Configuration configuration;
public DefaultSqlSessionFactory(Configuration configuration) {
this.configuration = configuration;
}
public SqlSession openSession() {
return this.openSessionFromDataSource(this.configuration.getDefaultExecutorType(), (TransactionIsolationLevel)null, false);
}
public Configuration getConfiguration() {
return this.configuration;
}
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
DefaultSqlSession var8;
var8 = new DefaultSqlSession(this.configuration, executor, autoCommit);
rturn var8;
}
}
DefaultSqlSessionFactory的构造函数中需要一个Configuration 类型的实例 这里我们知道这个就能自动联想到 SqlSessionFactoryBuilder.build()方法中 肯定有创建DefaultSqlSessionFactory的地方 我们进入SqlSessionFactoryBuilder这个类找找看:
public class SqlSessionFactoryBuilder {
public SqlSessionFactoryBuilder() {
}
//第一步 我们看见的build方法在这里
public SqlSessionFactory build(InputStream inputStream) {
return this.build((InputStream)inputStream, (String)null, (Properties)null);
}
//第二步 创建Configuration 对象
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
SqlSessionFactory var5;
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
} catch (Exception var14) {
throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException var13) {
;
}
}
return var5;
}
//第三步 创建DefaultSqlSessionFactory实例
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
很明显 这个build方法主要干只干了一件事,攒一个Configuration出来 顾名思义 Configuration就是我们在test-mybaits.xml中配置的信息,我们目前配置的信息有 数据库驱动、用户名、密码、mysql连接和一个我们用到的UserDao接口对应的Mapper,这里我们暂且不想xml如何转换为Configuration对象(一过脑子无非就是去xml文件中匹配各种标签,并将标签对应的键值赋值到Configuration的属性中,后面在继续阅读的过程中,发现必须阅读Configuration的部分解析过程),但已经解决了我们第一个疑问:1.mybaits怎么用到的配置文件 但是这一步mybatis仅仅是在做自己的事,好像和JDBC并无关联 我们暂且把后面的3个问题继续往后放
问题回顾:
2.mybaits怎么将接口和mapper整合在一起 调用时仿佛自己动态创建了UserDao的实现类
3.mybaits怎么将结果集转换为我们用到的实体类
4.既然mybaits封装了JDBC,那么和JDBC的代码是如何对应的
下面我们看
//初始化 SqlSession
SqlSession session =sqlSessionFactory.openSession();
干了什么事情 SqlSession可以理解成一次执行操作语句的会话 这里我们盲猜是在封装JDBC的
Connection connection = DriverManager.getConnection(url,uname,password);
但是事实证明我错了 这里笔者分别故意错误的修改了数据库连接地址,JDBC的DriverManager.getConnection时 jdbc抛出了超时的异常,而openSession依然没有报错而是在userDao.getUserById(17L)抛出了超时的异常。
进入openSession看源码 发现最后只是返回了一个DefaultSqlSession对象回来:
image.png
这里mybatis仍然在做自己的事情
来到
//反向代理获得UserDao 反向代理的过程中将 UserDao和 UserMapper.xml中的查询方法相关联
UserDao userDao = session.getMapper(UserDao.class);
先看看SqlSession是个啥东西
public interface SqlSession extends Closeable {
Configuration getConfiguration();
<T> T getMapper(Class<T> var1);
Connection getConnection();
}
同样 我筛选了只和流程代码相关的东西,getMapper方法 我们日常贴出他的实现类
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;
}
public Configuration getConfiguration() {
return this.configuration;
}
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
}
一大坨实现类的代码我们又精简了,噢,原来是通过Configuration 的getMapper方法来获取到的UserDao的实现类,这里我们贴出Configuration的代码:
public class Configuration {
protected final MapperRegistry mapperRegistry;
public Configuration() {
this.mapperRegistry = new MapperRegistry(this);
}
public <T> T getMapper(Class<T> type) {
return this.configuration.getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
}
发现getMapper方法实际上是调用的MapperRegistry 的getMapper方法,我们不得不了解一下MapperRegistry 这个Mapper注册类的结构
public class MapperRegistry {
private final Configuration config;
//键是接口的类型 值是代理的泛型工厂对象
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();
public MapperRegistry(Configuration config) {
this.config = config;
}
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);
}
}
}
public <T> boolean hasMapper(Class<T> type) {
return this.knownMappers.containsKey(type);
}
public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
this.knownMappers.put(type, new MapperProxyFactory(type));
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
}
MapperRegistry 中包三个主要功能:
1.含一个实例名为knownMappers的HashMap 用来存储从配置文件解析到的 接口名----与代理工厂的 全局变量
2.getMapper方法 获取某个 entry
3.addMapper方法
这里我们不禁想到,我们调用的是getMapper方法来获取UserDao接口的实例,但是我们是在什么时间点调用的addMapper呢?我们想一下,肯定是在mybaits加载配置文件时干的,因为我们在test-mybaits里指定过mapper文件的地址,在mapper中又指定了对应接口的命名空间
test-mybaits.xml:
<mappers>
<mapper resource="com/test/mapper/UserMapper.xml"/>
</mappers>
UserMapper.xml
<mapper namespace="com.test.dao.UserDao">
<select id="getUserById" resultType="com.test.enity.Users">
select * from users where id = #{id}
</select>
</mapper>
也就是mybatis将配置文件在开始时都解析完备并且放入内存中了,一共没几行的代码跑不了
SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
这段儿了 层层深入我们会发现XMLConfigBuilder中的mapperElement方法中包含这么一段儿
深入过程(已经眼累的同学可以跳过)
1.SqlSessionFactoryBuilder.build()
2.XMLConfigBuilder.parse()
XMLConfigBuilder.parseConfiguration()
XMLConfigBuilder.mapperElement()
3.Configuration.addMapper()
4.MapperRegistry.addMapper(Type type)
{
MapperRegistry.knownMappers.put(type, new MapperProxyFactory(type));
}
private void mapperElement(XNode parent) throws Exception {
.........
.........
Class<?> mapperInterface = Resources.classForName(mapperClass);
//addMapper就是在调用MapperRegistry里的kownsMappers
this.configuration.addMapper(mapperInterface);
}
找到了这个接口的类型 并调用addMapper方法将它放入了knownMappers的HashMap
的add方法,这里和解析xml配置有关,我们先不深究。
我们深入getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//knownMappers的键为Class的类型,值为MapperProxyFactory(Mapper代理工厂)的一个对象
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);
}
}
}
knownMappers的键为Class的类型,值为MapperProxyFactory(Mapper代理工厂)的一个对象,我们看到了Proxy字样,也就是mybaits一直提的使用动态代理的地方,knownMappers键是让我们能找想实例化的对象的类型,值是我们能把这个对象实例化所用到的方法,到我们深入一下,脱下MapperProxyFactory裤子:
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
//addMapper时 knownMappers调用的此构造函数
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return this.methodCache;
}
//主要看这段儿 利用动态代理来创建了接口的实现类
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
}
我们知道Proxy.newProxyInstance方法的第三个参数是实现了InvocationHandler接口的代理对象(这句话如果有疑问请复习一下动态代理的过程以及目的是什么),这个泛型的MapperProxyFactory代理对象工厂正是帮我们实例化出来要生成的代理对象,接下来我们将思路从工厂类转移到代理对象类,就像牛仔裤里套了一层棉裤一样,我们再脱下MapperProxy类这条裤子:
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
//主要方法看这里:实现了接口的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//为代理的方法做了缓存
MapperMethod mapperMethod = this.cachedMapperMethod(method);
return mapperMethod.execute(this.sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
我们主要关注的是实现InvocationHandler接口的invoke方法中
mapperMethod.execute(this.sqlSession, args)都干了些什么东西(如何去执行我们的sql语句的)
public class MapperMethod {
private final MapperMethod.SqlCommand command;
private final MapperMethod.MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object param;
Object result;
switch(this.command.getType()) {
case INSERT:
case UPDATE:
case DELETE:
case SELECT:
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
return result;
}
}
这里我们看到有一个swich循环判断的是SqlCommand的type属性,目前我们的select的属性的枚举类型为SELECT,因此调用了selectOne方法,DefaultSqlSession的selectOne方法实现如下:
public <T> T selectOne(String statement, Object parameter) {
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, RowBounds rowBounds) {
List var5;
try {
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception var9) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + var9, var9);
} finally {
ErrorContext.instance().reset();
}
return var5;
}
this.selectList方法直接返回了一个泛型集合,看来整个sql已经执行完毕了,下面这两段代码 我们需要深入追究一下:
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
我们又看到了熟悉的 this.configuration.getMappedStatement(statement);
(上一次是this.configuration.getMapper(type, this))
我们又需要关心一个在执行new SqlSessionFactoryBuilder().build(inputStream)时的一个HashMap
那么statement参数是一个字符串如下图:
image.png
Configuration类:
public class Configuration {
protected final Map<String, MappedStatement> mappedStatements;
}
键为我们上面看到的方法全路径,值为一个MappedStatement对象,此对象加载对应的坐标如下(心累的同学可以跳过):
1.SqlSessionFactoryBuilder.build()
2.XMLConfigBuilder.parse()
XMLConfigBuilder.parseConfiguration()
XMLConfigBuilder.mapperElement()
3.XMLMapperBuilder.parse()
XMLMapperBuilder.configurationElement()
XMLMapperBuilder.buildStatementFromContext()
4.XMLStatementBuilder.parseStatementNode()
5.MapperBuilderAssistant.addMappedStatement()
6.Configuration.addMappedStatement(statement)
image.png
看到这种流水账式并且hardcode的代码忽然倍感亲切(心累结束符)
到这里,已经把需要执行的sql语句整理完毕,放到
this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
去执行。
Executor是一个接口 ,里面定义了query方法它长这个样子:
//先进入此方法
public interface Executor {
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
}
BaseExecutor是Executor 的实现类,它在实现了query方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = this.createCacheKey(ms, parameter, rowBounds, boundSql);
//调用重载方法
return this.query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
return list;
}
}
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
return list;
}
protected abstract <E> List<E> doQuery(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, BoundSql var5) throws SQLException;
queryFromDatabase里的doQuery属于BaseExecutor中的抽象方法,我们找到它的实现类SimpleExecutor(别问我是如何找到这个类的 心累)
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
return var9;
}
query方法属于StatementHandler接口,它的实现类
SimpleStatementHandler实现方式如下:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = this.boundSql.getSql();
statement.execute(sql);
return this.resultSetHandler.handleResultSets(statement);
}
到这里我们终于看到了JDBC里熟悉的方法,动态代理的最终目的我们也就知道了:
解析出标签里的各项数据库配置和SQL最终生成一个Statement对象用来执行sql语句,这里需要补充一点的是,在prepareStatement是利用JdbcTransaction初始化事务处理器中的DataSource属性根据配置文件中配置的连接池名称,灵活的初始化Connections对象也就是连接池的创建。
具体路程如下:(这里由于对数据库连接池的创建有疑问 因此补充一下数据库连接池的创建过程)
//初始化SqlSessionFactory
SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
//初始化 SqlSession
SqlSession session =sqlSessionFactory.openSession();
① new SqlSessionFactoryBuilder().build方法完成对Configration的Environment属性赋值
Environment对象的属性如下
public final class Environment {
private final String id;
//事务工厂 JdbcTransactionFactory
private final TransactionFactory transactionFactory;
//PooledDataSource 连接池
private final DataSource dataSource;
}
对应编码:
private void environmentsElement(XNode context) throws Exception {
if (context != null) {
Iterator var2 = context.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String id = child.getStringAttribute("id");
//初始化事务 JdbcTransactionFactory 是根据配置文件的 <transactionManager type="JDBC"/>解析出来的
TransactionFactory txFactory = this.transactionManagerElement(child.evalNode("transactionManager"));
//初始化连接对象PooledDataSourceFactory 根据<dataSource type="POOLED">标签解析
DataSourceFactory dsFactory = this.dataSourceElement(child.evalNode("dataSource"));
DataSource dataSource = dsFactory.getDataSource();
Builder environmentBuilder = (new Builder(id)).transactionFactory(txFactory).dataSource(dataSource);
this.configuration.setEnvironment(environmentBuilder.build());
}
}
}
②在sqlSessionFactory.openSession()中的openSessionFromDataSource方法 根据Environment对象中的事务工厂来创建事务的实例
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);
//此处根据JdbcTransactionFactory 创建出来JdbcTransaction JdbcTransaction 中使用的是PooledDataSource
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
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;
}
至此我们已经解决了大部分问题:
1.mybaits怎么用到的配置文件
答:通过SqlSessionFactoryBuilder的build方法 利用XMLConfigBuilder的parse方法 将配置文件内容转换为
Configuration类 这个类中有想要的一些资源
2.mybaits怎么将接口和mapper整合在一起 调用时仿佛自己动态创建了UserDao的实现类
答:分为两步:
①在使用XMLConfigBuilder解析配置文件时预加载了需要 用到的接口 并将它们放到一个map中
②通过动态代理的方式将接口和实现类 整合在一起
3.mybaits怎么将结果集转换为我们用到的实体类(这个问题以后再说- -)
4.既然mybaits封装了JDBC,那么和JDBC的代码是如何对应的
DefaultSqlSession中 SimpleExecutor实现类的
prepareStatement()方法里通过数据库连接池代替了 jdbc数据库驱动
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Connection connection = this.getConnection(statementLog);
Statement stmt = handler.prepare(connection, this.transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
SimpleStatementHandler类中的query方法对应jdbc中的 statement.execute(sql);
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
String sql = this.boundSql.getSql();
statement.execute(sql);
return this.resultSetHandler.handleResultSets(statement);
}
写到这里已经很累了,如果想有个自己的理解请自行打断点阅读源码
用今日最后一丝力气想画出来一幅图来描述mybatis的几行代码做的事情
image.png
如果这篇文章帮助到你了,请来个小红心鼓励下哦~
网友评论