本篇文章主要是看了书籍如下几个章节后进行的笔记总结:
额,对于这个问题我能说看源码是为了面试吗???emmmmm,当然不能的。
阅读源码是一个优秀软件开发者必备的能力
很多时候我们接手一个前辈遗留项目的时候,这个时候千万不能慌,打开idea开始看代码,看有没有类似Thread.sleep的核心代码,如果有的话,那么我们就可以通过注释的方式优化系统。所以,熟悉一个项目就需要去看源码,了解整体的执行逻辑
和设计思想
,这样才能做到心中有数,是优化系统的基础。当然,自认为写的不错的旧代码也需要时常温习一下。
阅读源码可以学习大佬们的设计思想
平时很多小伙伴可能和我一样,整天CRUD,看了一堆的设计模式,就是不能很滑溜地用到我们的项目中;这个时候就可以看大佬的源码,看看他们是如何对一个功能进行设计的,用到了什么设计模式,然后能不能借鉴到自己的项目中去。
能发现自己的知识盲区,完善知识储备
很多时候,源码就是一本教材,里面有很多的知识点值得我们去挖掘,去学习。经过自己的深挖,可能会发现一片蓝天,正所谓“知道的越多,不知道的越多”
。
能快速定位BUG
当对一个框架、项目、系统完全熟悉之后,代码就像是自己写的一样,对于遇到的问题可以快速定位到问题
二次开发需要
有些时候某些框架不能满足公司的个性化需求,这个时候就需要对框架进行二次开发,二次开发的前提就是,你需要对框架的核心模块很熟悉,不然真的就是盲人摸象
了。
在书中,作者也提了一些,比如:
其实,还有很多其他优秀的开源框架,比如RocketMQ、MyBatis-Plus、Nacos等,今天把话撂这了,上面提到的框架源码,后面都盘它一遍。
算了,感觉一次性确实盘不完,所以,我决定了,以后还是围绕MyBatis、Spring、SpringMVC、SpringBoot源码展开,不然车开太快会翻车的。
public class UserDao {
private static final String driver = "com.mysql.cj.jdbc.Driver";
private static final String url = "jdbc:mysql://localhost:3306/test_mybatis?allowPublicKeyRetrieval=true";
private static final String userName = "root";
private static final String password = "12345678";
public static Connection getConnection() {
Connection connection = null;
try {
Class.forName(driver);// 1.注册驱动
connection = DriverManager.getConnection(url, userName, password);// 2.获取连接对象
} catch (Exception e) {
e.printStackTrace();
}
return connection;
}
// 测试查询user列表
public static void testSelectUser() {
Connection connection = getConnection();// 获取connection对象
if (connection == null) {
System.err.println("获取connection对象出现异常");
return;
}
Statement statement = null;
try {
statement = connection.createStatement();// 3.创建statement对象
String sql = "select * from user";
ResultSet resultSet = statement.executeQuery(sql);// 4.使用statement对象执行SQL语句
List<User> users = new ArrayList<>();
while (resultSet.next()) {// 5.处理resultSet结果集
User user = new User();
user.setId(resultSet.getInt("id"));
user.setName(resultSet.getString("name"));
user.setAge(resultSet.getInt("age"));
users.add(user);
}
System.out.println(users);// 打印处理结果
} catch (Exception e) {
e.printStackTrace();
} finally {
if (statement != null) {
try {
statement.close();// 6.关闭资源
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
connection.close();// 7.关闭资源
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// 修改操作
public static void updateUserByUser(User user) {
Connection connection = getConnection();
if (connection == null) {
System.err.println("获取connection对象出现异常");
return;
}
String sql = "update user set age = ?,name = ? where id = ?";
PreparedStatement statement = null;
try {
statement = connection.prepareStatement(sql);// 创建statement
statement.setInt(1, user.getAge());// 设置参数
statement.setString(2, user.getName());
statement.setInt(3, user.getId());
statement.executeUpdate();// 执行sql
} catch (Exception e) {
e.printStackTrace();
} finally {
if (statement != null) {
try {
statement.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
testSelectUser();
}
}
总结为如下核心步骤:
从上面可以看出,第4、5、6步是非常麻烦的,只能针对不同对象的不同操作拼装不同的操作语句然后单独处理返回的结果。一般的项目中对数据库的读写操作是非常频繁的,所以就会产生大量的工作,这个这也是MyBatis重点优化的部分。
ORM简称Object Relational Mapping,也就是对象关系映射
,下面这张图画的很直接:
就比如当前我有一个User对象,对象的字段为:
public class User {
private Integer id;
private String userName;.// 驼峰命名
private Integer userAge;// 驼峰命名
}
有一个User表,表中的字段为:
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`user_name` char(255) COLLATE utf8_bin DEFAULT NULL,
`user_age` int DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=42 DEFAULT CHARSET=utf8mb3 COLLATE=utf8_bin;
可以看到,user_name、user_age与对象User中的userName、userAge不能一一对应上,所以就使用ORM框架进行自动映射,常见的ORM框架有如下几个:Hibernate、MyBatis、JFinal等
创建一个实体对象User:
public class User {
private Integer id;
private String name;
private Integer age;
省略get、set、toString方法
}
创建一个UserMapper接口:
public interface UserMapper {
public User getUserByName(String name);
@Select("select * from user where id = #{userId}")
public User getUserById(@Param("userId") Integer userId);
}
在resources文件下创建一个mapper文件夹,并创建UserMapper.xml:
DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.mytest.mapper.UserMapper">
<select id="getUserByName" resultType="user">
select *
from user
where name = #{name}
select>
mapper>
在resources文件夹下创建mybatis-config.xml文件:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="org.apache.ibatis.mytest.pojo"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/test_mybatis?allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="12345678"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/UserMapper.xml">mapper>
mappers>
configuration>
创建使用框架使用类:
public static void main(String[] args) throws IOException {
Reader resourceAsReader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
SqlSession sqlSession = sessionFactory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User userById = mapper.getUserById(4);
System.out.println(userById);
}
查看执行结果:
使用方便
从上面可以看出,直接使用User userById = mapper.getUserById(4)就可以得到执行的结果,并且可以自定义配置结果映射等
能方便书写动态SQL
支持在xml中进行动态SQL的编写,比如:
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
when>
<otherwise>
AND featured = 1
otherwise>
choose>
select>
支持更多的功能
Reader resourceAsReader = Resources.getResourceAsReader("mybatis-config.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(resourceAsReader);
最后追踪代码会进入到一个build重载方法中:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
// 1.parser.parse()
// 2.build(parser.parse())
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
reader.close();// 关闭流
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
这里有两个核心的步骤:
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
// 避免xml文件再次被解析
parsed = true;
// 执行解析方法
parseConfiguration(parser.evalNode("/configuration"));
return configuration;// 返回一个Configuration对象
}
// 解析配置文件的各个标签信息并存放到Configuration对象中
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
// 将settings配置到configuration的各个配置中
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
// Java类型与jdbc类型的映射关系
typeHandlerElement(root.evalNode("typeHandlers"));
// mapper配置相关
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
可以和mybatis.xml文件对比着查看代码,可以发现是和源码中是对应的:
DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<properties resource="config.properties">
<property name="password" value="12345678"/>
properties>
<typeAliases>
<package name="org.apache.ibatis.mytest.pojo"/>
typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/test_mybatis?allowPublicKeyRetrieval=true"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
dataSource>
environment>
environments>
<mappers>
<mapper resource="mapper/UserMapper.xml">mapper>
mappers>
configuration>
从上面的代码可以看出,parser.parse()主要功能就是解析配置文件的信息,并将得到的信息存放到Configuration
对象中去,这里可以随便查看一个解析核心类,这里就以 为例:
<typeAliases>
<package name="org.apache.ibatis.mytest.pojo"/>
typeAliases>
typeAliasesElement(XNode parent)方法源码:
private void typeAliasesElement(XNode parent) {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String typeAliasPackage = child.getStringAttribute("name");
// 注册到类型别名处理映射器中(最终还是存到configuration对象中)
configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
} else {
String alias = child.getStringAttribute("alias");
String type = child.getStringAttribute("type");
try {
Class<?> clazz = Resources.classForName(type);
if (alias == null) {
// 如果alias = "" 或者为空,会自动解析 类上有没有ALias注解,如果注解都没得,直接使用类名
typeAliasRegistry.registerAlias(clazz);
} else {
typeAliasRegistry.registerAlias(alias, clazz);//typeAliasRegistry也是存放到Configuration对象中的
}
} catch (ClassNotFoundException e) {
throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
}
}
}
}
}
上面谈了解析的数据都会以不同的形式存放到一个比较重的对象configuration中,我们可以简单看看这个对象,后面的源码中会经常使用到这个对象:
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
// 然后通过parser.parse()返回的configuration对象作为参数创建一个DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
简单看看DefaultSqlSessionFactory对象:
SqlSession sqlSession = sessionFactory.openSession();
||
||
\ /
\/
@Override
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
||
||
\ /
\/
// 通过DataSource来创建session
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();
}
}
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
||
||
\ /
\/
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
||
||
\ /
\/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
||
||
\ /
\/
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);// 生成代理对象,后面在阅读源码的时候会涉及到
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
User userById = mapper.getUserById(4);// 会执行到代理对象的invoke方法中
||
||
\ /
\/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 调用mapper中方法会执行到这里
try {
if (Object.class.equals(method.getDeclaringClass())) {// 判断方法是不是一个Object,一般来说都不是的
return method.invoke(this, args);
} else {
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);// 一般来说会进入到这个分支中执行后续的逻辑,执行真正的CRUD操作,这里面涉及到创建connection、statement、缓存、插件、参数设置、结果集映射等细活,后面会涉及到
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
@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) {// 核心的执行方法,用于执行sql
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 {// 走到这里,因为我们的查询只返回一个user
Object param = method.convertArgsToSqlCommandParam(args);// 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;
}
@Override
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;
}
}
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 根据Mapper的ID获取对应的MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 执行query操作
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
看看debug结果:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);// 此时的SQL语句还是 select * from user where id = ?
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);// 创建缓存的key
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 继续往下整
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();// 直接得到缓存
if (cache != null) {// 由于我们这里没开启二级缓存,所以cache对象为null
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 会直接执行这里。继续整
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;// 查询栈
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;//从一级缓存中拿数据,但是是多久将值放到localCache中的呢??
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 <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;
}
@Override
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();
// 获取statementType对应的StatementHandler处理对象
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 创建Statement对象,设置statement参数等操作
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);// 先看这里,继续整
} finally {
closeStatement(stmt);
}
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行拼接好的SQL语句
ps.execute();
// 处理结果集
return resultSetHandler.handleResultSets(ps);
}
根据配置文件的位置,获取Reader对象或者输入流InputStream
从配置文件的根节点开始,逐层解析配置文件,也包括相关的映射文化,在解析的过程中不断将解析结果放入Configuration中
将配置好的Configuration对象作为参数,创建一个SqlSessionFactory对象
通过SqlSessionFactory对象创建一个SqlSession,并通过Sqlsession得到mapper的代理对象
使用mapper调用具体的方式去执行SQL语句
根据接口路径名和方法名从configuration中拿到MappedStatement对象
查询二级缓存,二级缓存没有从一级缓存中查询,一级缓存也没有就查询DB
然后创建StatementHandler并处理参数映射(将SQL语句中的?替换为 具体的值)
通过PreparedStatement执行SQL查询DB
执行完成后,通过resultSetHandler处理结果集并返回
最后缓存配置的参数,将结果存放到一、二级缓存中
额,是不是发现很多包,是不是不想看下去了???再等等,马上会有惊喜。
虽然看着很多的包,但是是有规律可寻的,我们可以对包进行一个简单的分类:
感觉大家的阅读,今天就给各位老铁分享到这里,后面我会继续啃书,把书中涉及到的核心点以问题
的形式都给大家整理出来;带着问题去看书,会有事半功倍的效果,不信???可以试一试。
欢迎大家持续关注落魄哥的博客《落程魄序》
,大家一起看书,一起学习,一起成长!!!