JDBC的操作过程

1.导包

  1. <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
  2. <dependency>
  3. <groupId>mysql</groupId>
  4. <artifactId>mysql-connector-java</artifactId>
  5. <version>8.0.16</version>
  6. </dependency>

过程:类加载器->注册驱动->创建连接->创建statement->执行sql->查看结果集->关闭流

  1. try {
  2. Class.forName("com.mysql.cj.jdbc.Driver");
  3. } catch (ClassNotFoundException e) {
  4. e.printStackTrace();
  5. }
  6. Connection connection = null;
  7. Statement statement = null;
  8. ResultSet resultSet = null;
  9. try {
  10. String url = "jdbc:mysql://localhost:3306/league?useAffectedRows=true&rewriteBatchedStatements=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8";
  11. String userName = "root";
  12. String password = "root";
  13. connection = DriverManager.getConnection(url, userName, password);
  14. statement = connection.createStatement();
  15. resultSet = statement.executeQuery("SELECT * FROM t_user");
  16. while (resultSet.next()) {
  17. User user = new User();
  18. int id = resultSet.getInt(1);
  19. user.setId(id);
  20. }
  21. } catch (SQLException e) {
  22. e.printStackTrace();
  23. } finally {
  24. if (resultSet != null){
  25. try {
  26. resultSet.close();
  27. } catch (SQLException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. if (statement != null){
  32. try {
  33. statement.close();
  34. } catch (SQLException e) {
  35. e.printStackTrace();
  36. }
  37. }
  38. if (connection != null){
  39. try {
  40. connection.close();
  41. } catch (SQLException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. }

由上述可知,JDBC是原生访问数据库的方式, 渐渐的对 JDBC 不同程度的包装 访问数据库比较麻烦, 代码重复度极高,我们查询数据的返回的数据,以及映射javaBean都比较麻烦,,因此就有了DbUtils,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,创建连接、结果集封装、释放资源,同时也不会影响程序的性能。

  1. <!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
  2. <dependency>
  3. <groupId>commons-dbutils</groupId>
  4. <artifactId>commons-dbutils</artifactId>
  5. <version>1.6</version>
  6. </dependency>

能自动封装查询结果集, 需要在代码中写 sql 语句。
Mybatis: 进一步封装 jdbc, Sql 语句写在配置文件中, 面向对象操作, 有一 二级缓存功能。
下面我们了解一下mybatis官方文档实现过程:
接下来我们带着3个问题去看:
1.mybatis如何获取数据源?
2.mybatis如何执行sql?
3.mybatis如何操作数据库?
不知从那里开始看mybatis源码,首先我们看官网,每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。因此我们仅从XML 中构建 SqlSessionFactory出发。
XML配置文件

  1. <?xml version="1.0" encoding="UTF-8" ?>
  2. <!DOCTYPE configuration
  3. PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  4. "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6. <!-- spring整合后 environments配置将废除 -->
  7. <environments default="development">
  8. <environment id="development">
  9. <!-- 使用jdbc事务管理 -->
  10. <transactionManager type="JDBC"/>
  11. <dataSource type="POOLED">
  12. <!-- 数据库连接池 -->
  13. <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
  14. <property name="url" value="jdbc:mysql://39.108.185.XXX/bay_pay"/>
  15. <property name="username" value="root"/>
  16. <property name="password" value="123456"/>
  17. <!--<property name="driver" value="${driver}"/>-->
  18. <!--<property name="url" value="${url}"/>-->
  19. <!--<property name="username" value="${username}"/>-->
  20. <!--<property name="password" value="${password}"/>-->
  21. </dataSource>
  22. </environment>
  23. </environments>
  24. <mappers>
  25. <mapper resource="mapper/UserMapper.xml"/>
  26. </mappers>
  27. </configuration>
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
  3. <mapper namespace="org.apache.ibatis.demo.UserMapper">
  4. <select id="selectUser" resultType="org.apache.ibatis.demo.User">
  5. select * from user where id= #{id}
  6. </select>
  7. </mapper>

从一个main方法开始,查看源码的入口:

  1. public static void main(String[] args) throws IOException {
  2. String resource = "mybatis-config.xml";
  3. InputStream inputStream = Resources.getResourceAsStream(resource);
  4. // 构建sqkSessionFactory
  5. SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
  6. // 创建sqlSession
  7. SqlSession sqlSession = sqlSessionFactory.openSession();
  8. User user = sqlSession.selectOne("org.apache.ibatis.demo.UserMapper.selectUser", 1);
  9. System.out.println(user);
  10. sqlSession.close();
  11. sqlSession.commit();
  12. }

构建一个sqlSessionfactory

  1. public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  2. try {
  3. //解析mybatis-config.xml
  4. //XMLConfigBuilder 构造者
  5. XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  6. //parse(): 解析mybatis-config.xml里面的节点
  7. return build(parser.parse());
  8. } catch (Exception e) {
  9. throw ExceptionFactory.wrapException("Error building SqlSession.", e);
  10. } finally {
  11. ErrorContext.instance().reset();
  12. try {
  13. inputStream.close();
  14. } catch (IOException e) {
  15. // Intentionally ignore. Prefer previous error.
  16. }
  17. }
  18. }

进入parser.parse()方法:

  1. public Configuration parse() {
  2. // 查看该文件是否已经解析过
  3. if (parsed) {
  4. throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  5. }
  6. // 如果没有解析过,则继续往下解析,并且将标识符置为true
  7. parsed = true;
  8. // 解析<configuration>节点
  9. parseConfiguration(parser.evalNode("/configuration"));
  10. return configuration;
  11. }

沿着这个流程可以看到返回了一个configration的类,这个类就是我们的xml中的所有属性转换为对象
进入parseConfiguration(),主要关注下mappers节点;

  1. private void parseConfiguration(XNode root) {
  2. try {
  3. // 解析<Configuration>下的节点
  4. // issue #117 read properties first
  5. propertiesElement(root.evalNode("properties"));
  6. Properties settings = settingsAsProperties(root.evalNode("settings"));
  7. loadCustomVfs(settings);
  8. loadCustomLogImpl(settings);
  9. // 别名<typeAliases>解析
  10. // 所谓别名 其实就是把你指定的别名对应的class存储在一个Map当中
  11. typeAliasesElement(root.evalNode("typeAliases"));
  12. // 插件 <plugins>
  13. pluginElement(root.evalNode("plugins"));
  14. // 自定义实例化对象的行为<objectFactory>
  15. objectFactoryElement(root.evalNode("objectFactory"));
  16. // MateObject 方便反射操作实体类的对象
  17. objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
  18. reflectorFactoryElement(root.evalNode("reflectorFactory"));
  19. settingsElement(settings);
  20. // read it after objectFactory and objectWrapperFactory issue #631
  21. environmentsElement(root.evalNode("environments"));
  22. databaseIdProviderElement(root.evalNode("databaseIdProvider"));
  23. typeHandlerElement(root.evalNode("typeHandlers"));
  24. // 主要 <mappers> 指向我们存放SQL的userMapper.xml文件
  25. mapperElement(root.evalNode("mappers"));
  26. } catch (Exception e) {
  27. throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  28. }
  29. }

断点看一下root.evalNode(“mappers”)的值:

  1. <mappers>
  2. <mapper resource="mapper/UserMapper.xml"/>
  3. </mappers>

懂了,这里就是解析我们的配置文件中的mappers标签。重点来了,解析mappers有几种方式?是怎么解析的?

  1. private void mapperElement(XNode parent) throws Exception {
  2. if (parent != null) {
  3. // 遍历解析mappers下的节点
  4. for (XNode child : parent.getChildren()) {
  5. // 首先解析package节点
  6. if ("package".equals(child.getName())) {
  7. // 获取包名
  8. String mapperPackage = child.getStringAttribute("name");
  9. configuration.addMappers(mapperPackage);
  10. } else {
  11. // 如果不存在package节点,那么扫描mapper节点
  12. // resource/url/mapperClass三个值只能有一个值是有值的
  13. String resource = child.getStringAttribute("resource");
  14. String url = child.getStringAttribute("url");
  15. String mapperClass = child.getStringAttribute("class");
  16. // 优先级 resource>url>mapperClass
  17. if (resource != null && url == null && mapperClass == null) {
  18. //如果mapper节点中的resource不为空
  19. ErrorContext.instance().resource(resource);
  20. // 那么直接加载resource指向的XXXMapper.xml文件为字节流
  21. InputStream inputStream = Resources.getResourceAsStream(resource);
  22. // 通过XMLMapperBuilder解析XXXMapper.xml,可以看到这里构建的XMLMapperBuilde还传入了configuration,所以之后肯定是会将mapper封装到configuration对象中去的。
  23. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  24. // 解析
  25. mapperParser.parse();
  26. } else if (resource == null && url != null && mapperClass == null) {
  27. // 如果url!=null,那么通过url解析
  28. ErrorContext.instance().resource(url);
  29. InputStream inputStream = Resources.getUrlAsStream(url);
  30. XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
  31. mapperParser.parse();
  32. } else if (resource == null && url == null && mapperClass != null) {
  33. // 如果mapperClass!=null,那么通过加载类构造Configuration
  34. Class<?> mapperInterface = Resources.classForName(mapperClass);
  35. configuration.addMapper(mapperInterface);
  36. } else {
  37. // 如果都不满足 则直接抛异常 如果配置了两个或三个 直接抛异常
  38. throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
  39. }
  40. }
  41. }
  42. }
  43. }

此方法中的入参:XNode parent就是上一步获取的root.evalNode(“mappers”),for循环遍历parent.getChildren()

  1. <!-- 通过配置文件路径 -->
  2. <mapper resource="mapper/UserMapper.xml"/>
  3. <!-- 通过Java全限定类名 -->
  4. <mapper class="com.mybatistest.TestMapper"/>
  5. <!-- 通过url 通常是mapper不在本地时用 -->
  6. <mapper url=""/>
  7. <!-- 通过包名 -->
  8. <package name="com.ibatis"/>

优先级别最高的就是package,其次就是resource,url,class,三者中只能有一个不能为空,否则将抛异常。
注意:mapper节点中,可以使用resource/url/class三种方式获取mapper
用resource来加载mapper.xml,通过mapperParser.parse()方法看一下它的解析过程。

  1. public void parse() {
  2. //判断文件是否之前解析过
  3. if (!configuration.isResourceLoaded(resource)) {
  4. //解析mapper文件节点(主要)(下面贴了代码)
  5. configurationElement(parser.evalNode("/mapper"));
  6. configuration.addLoadedResource(resource);
  7. //绑定Namespace里面的Class对象
  8. bindMapperForNamespace();
  9. }
  10. //重新解析之前解析不了的节点,先不看,最后填坑。
  11. parsePendingResultMaps();
  12. parsePendingCacheRefs();
  13. parsePendingStatements();
  14. }
  1. // 解析mapper文件里面的节点
  2. // 拿到里面配置的配置项 最终封装成一个MapperedStatemanet
  3. private void configurationElement(XNode context) {
  4. try {
  5. // 获取命名空间 namespace,这个很重要,后期mybatis会通过这个动态代理我们的Mapper接口
  6. String namespace = context.getStringAttribute("namespace");
  7. if (namespace == null || namespace.equals("")) {
  8. //如果namespace为空则抛一个异常
  9. throw new BuilderException("Mapper's namespace cannot be empty");
  10. }
  11. builderAssistant.setCurrentNamespace(namespace);
  12. // 解析缓存节点
  13. cacheRefElement(context.evalNode("cache-ref"));
  14. cacheElement(context.evalNode("cache"));
  15. // 解析parameterMap(过时)和resultMap <resultMap></resultMap>
  16. parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  17. resultMapElements(context.evalNodes("/mapper/resultMap"));
  18. // 解析<sql>节点
  19. //<sql id="staticSql">select * from test</sql> (可重用的代码段)
  20. //<select> <include refid="staticSql"></select>
  21. sqlElement(context.evalNodes("/mapper/sql"));
  22. // 解析增删改查节点<select> <insert> <update> <delete>
  23. buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  24. } catch (Exception e) {
  25. throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  26. }

上述解析了各种节点,在XXXMapper.xml中必不可少的就是编写SQL,与数据库交互主要靠的也就是这个,所以着重说说解析增删改查节点的方法——buildStatementFromContext()。
在没贴代码之前,根据这个名字就可以略知一二了,这个方法会根据我们的增删改查节点,来构造一个Statement,而用过原生Jdbc的都知道,Statement就是我们操作数据库的对象。

  1. private void buildStatementFromContext(List<XNode> list) {
  2. if (configuration.getDatabaseId() != null) {
  3. buildStatementFromContext(list, configuration.getDatabaseId());
  4. }
  5. //解析xml
  6. buildStatementFromContext(list, null);
  7. }
  1. private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  2. for (XNode context : list) {
  3. final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
  4. try {
  5. // 解析xml节点
  6. statementParser.parseStatementNode();
  7. } catch (IncompleteElementException e) {
  8. // xml语句有问题时 存储到集合中 等解析完能解析的再重新解析
  9. configuration.addIncompleteStatement(statementParser);
  10. }
  11. }
  12. }
  1. public void parseStatementNode() {
  2. // 获取<select id="xxx">中的id
  3. String id = context.getStringAttribute("id");
  4. //获取databaseId 用于多数据库,这里为null
  5. String databaseId = context.getStringAttribute("databaseId");
  6. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
  7. return;
  8. }
  9. //获取节点名 select update delete insert
  10. String nodeName = context.getNode().getNodeName();
  11. //根据节点名,得到SQL操作的类型
  12. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  13. //判断是否是查询
  14. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  15. //是否刷新缓存 默认:增删改刷新 查询不刷新
  16. boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  17. //是否使用二级缓存 默认值:查询使用 增删改不使用
  18. boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  19. //是否需要处理嵌套查询结果 group by
  20. // 三组数据 分成一个嵌套的查询结果
  21. boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
  22. // Include Fragments before parsing
  23. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  24. //替换Includes标签为对应的sql标签里面的值
  25. includeParser.applyIncludes(context.getNode());
  26. //获取parameterType名
  27. String parameterType = context.getStringAttribute("parameterType");
  28. //获取parameterType的Class
  29. Class<?> parameterTypeClass = resolveClass(parameterType);
  30. //解析配置的自定义脚本语言驱动 这里为null
  31. String lang = context.getStringAttribute("lang");
  32. LanguageDriver langDriver = getLanguageDriver(lang);
  33. // Parse selectKey after includes and remove them.
  34. //解析selectKey
  35. processSelectKeyNodes(id, parameterTypeClass, langDriver);
  36. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  37. //设置主键自增规则
  38. KeyGenerator keyGenerator;
  39. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  40. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  41. if (configuration.hasKeyGenerator(keyStatementId)) {
  42. keyGenerator = configuration.getKeyGenerator(keyStatementId);
  43. } else {
  44. keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
  45. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
  46. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  47. }
  48. /************************************************************************************/
  49. //解析Sql(重要) 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析
  50. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  51. //获取StatementType,可以理解为Statement和PreparedStatement
  52. StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  53. //没用过
  54. Integer fetchSize = context.getIntAttribute("fetchSize");
  55. //超时时间
  56. Integer timeout = context.getIntAttribute("timeout");
  57. //已过时
  58. String parameterMap = context.getStringAttribute("parameterMap");
  59. //获取返回值类型名
  60. String resultType = context.getStringAttribute("resultType");
  61. //获取返回值烈性的Class
  62. Class<?> resultTypeClass = resolveClass(resultType);
  63. //获取resultMap的id
  64. String resultMap = context.getStringAttribute("resultMap");
  65. //获取结果集类型
  66. String resultSetType = context.getStringAttribute("resultSetType");
  67. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  68. if (resultSetTypeEnum == null) {
  69. resultSetTypeEnum = configuration.getDefaultResultSetType();
  70. }
  71. String keyProperty = context.getStringAttribute("keyProperty");
  72. String keyColumn = context.getStringAttribute("keyColumn");
  73. String resultSets = context.getStringAttribute("resultSets");
  74. // 将刚才获取到的属性,封装成MappedStatement对象(代码贴在下面)
  75. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  76. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  77. resultSetTypeEnum, flushCache, useCache, resultOrdered,
  78. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  79. }

第一步骤的总结:将xml中的各个节点(id,parameterType,resultType)进行解析,构造了一个MappedStatement对象,这个对象很复杂,MyBatis使用了构造者模式来构造这个对象,最后当MappedStatement对象构造完成后,将其封装到全局Configuration对象中,第一阶段结束。

  1. <select id="selectUser" parameterType="java.lang.Integer" resultType="org.apache.ibatis.demo.User">
  2. select * from user where id= #{id}
  3. </select>

面试题:
解析mappers文件有几种方式?
4种
执行器有几种方式?
3中 简单的复用的 批量的