JDBC的操作过程
1.导包
<!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.16</version></dependency>
过程:类加载器->注册驱动->创建连接->创建statement->执行sql->查看结果集->关闭流
try {Class.forName("com.mysql.cj.jdbc.Driver");} catch (ClassNotFoundException e) {e.printStackTrace();}Connection connection = null;Statement statement = null;ResultSet resultSet = null;try {String url = "jdbc:mysql://localhost:3306/league?useAffectedRows=true&rewriteBatchedStatements=true&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=GMT%2B8";String userName = "root";String password = "root";connection = DriverManager.getConnection(url, userName, password);statement = connection.createStatement();resultSet = statement.executeQuery("SELECT * FROM t_user");while (resultSet.next()) {User user = new User();int id = resultSet.getInt(1);user.setId(id);}} catch (SQLException e) {e.printStackTrace();} finally {if (resultSet != null){try {resultSet.close();} catch (SQLException e) {e.printStackTrace();}}if (statement != null){try {statement.close();} catch (SQLException e) {e.printStackTrace();}}if (connection != null){try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}
由上述可知,JDBC是原生访问数据库的方式, 渐渐的对 JDBC 不同程度的包装 访问数据库比较麻烦, 代码重复度极高,我们查询数据的返回的数据,以及映射javaBean都比较麻烦,,因此就有了DbUtils,它是对JDBC的简单封装,学习成本极低,并且使用dbutils能极大简化jdbc编码的工作量,创建连接、结果集封装、释放资源,同时也不会影响程序的性能。
<!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils --><dependency><groupId>commons-dbutils</groupId><artifactId>commons-dbutils</artifactId><version>1.6</version></dependency>
能自动封装查询结果集, 需要在代码中写 sql 语句。
Mybatis: 进一步封装 jdbc, Sql 语句写在配置文件中, 面向对象操作, 有一 二级缓存功能。
下面我们了解一下mybatis官方文档实现过程:
接下来我们带着3个问题去看:
1.mybatis如何获取数据源?
2.mybatis如何执行sql?
3.mybatis如何操作数据库?
不知从那里开始看mybatis源码,首先我们看官网,每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。因此我们仅从XML 中构建 SqlSessionFactory出发。
XML配置文件
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"><configuration><!-- 和spring整合后 environments配置将废除 --><environments default="development"><environment id="development"><!-- 使用jdbc事务管理 --><transactionManager type="JDBC"/><dataSource type="POOLED"><!-- 数据库连接池 --><property name="driver" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://39.108.185.XXX/bay_pay"/><property name="username" value="root"/><property name="password" value="123456"/><!--<property name="driver" value="${driver}"/>--><!--<property name="url" value="${url}"/>--><!--<property name="username" value="${username}"/>--><!--<property name="password" value="${password}"/>--></dataSource></environment></environments><mappers><mapper resource="mapper/UserMapper.xml"/></mappers></configuration>
<?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="org.apache.ibatis.demo.UserMapper"><select id="selectUser" resultType="org.apache.ibatis.demo.User">select * from user where id= #{id}</select></mapper>
从一个main方法开始,查看源码的入口:
public static void main(String[] args) throws IOException {String resource = "mybatis-config.xml";InputStream inputStream = Resources.getResourceAsStream(resource);// 构建sqkSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 创建sqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();User user = sqlSession.selectOne("org.apache.ibatis.demo.UserMapper.selectUser", 1);System.out.println(user);sqlSession.close();sqlSession.commit();}
构建一个sqlSessionfactory
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {try {//解析mybatis-config.xml//XMLConfigBuilder 构造者XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);//parse(): 解析mybatis-config.xml里面的节点return build(parser.parse());} catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);} finally {ErrorContext.instance().reset();try {inputStream.close();} catch (IOException e) {// Intentionally ignore. Prefer previous error.}}}
进入parser.parse()方法:
public Configuration parse() {// 查看该文件是否已经解析过if (parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");}// 如果没有解析过,则继续往下解析,并且将标识符置为trueparsed = true;// 解析<configuration>节点parseConfiguration(parser.evalNode("/configuration"));return configuration;}
沿着这个流程可以看到返回了一个configration的类,这个类就是我们的xml中的所有属性转换为对象
进入parseConfiguration(),主要关注下mappers节点;
private void parseConfiguration(XNode root) {try {// 解析<Configuration>下的节点// issue #117 read properties firstpropertiesElement(root.evalNode("properties"));Properties settings = settingsAsProperties(root.evalNode("settings"));loadCustomVfs(settings);loadCustomLogImpl(settings);// 别名<typeAliases>解析// 所谓别名 其实就是把你指定的别名对应的class存储在一个Map当中typeAliasesElement(root.evalNode("typeAliases"));// 插件 <plugins>pluginElement(root.evalNode("plugins"));// 自定义实例化对象的行为<objectFactory>objectFactoryElement(root.evalNode("objectFactory"));// MateObject 方便反射操作实体类的对象objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));reflectorFactoryElement(root.evalNode("reflectorFactory"));settingsElement(settings);// read it after objectFactory and objectWrapperFactory issue #631environmentsElement(root.evalNode("environments"));databaseIdProviderElement(root.evalNode("databaseIdProvider"));typeHandlerElement(root.evalNode("typeHandlers"));// 主要 <mappers> 指向我们存放SQL的userMapper.xml文件mapperElement(root.evalNode("mappers"));} catch (Exception e) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);}}
断点看一下root.evalNode(“mappers”)的值:
<mappers><mapper resource="mapper/UserMapper.xml"/></mappers>
懂了,这里就是解析我们的配置文件中的mappers标签。重点来了,解析mappers有几种方式?是怎么解析的?
private void mapperElement(XNode parent) throws Exception {if (parent != null) {// 遍历解析mappers下的节点for (XNode child : parent.getChildren()) {// 首先解析package节点if ("package".equals(child.getName())) {// 获取包名String mapperPackage = child.getStringAttribute("name");configuration.addMappers(mapperPackage);} else {// 如果不存在package节点,那么扫描mapper节点// resource/url/mapperClass三个值只能有一个值是有值的String resource = child.getStringAttribute("resource");String url = child.getStringAttribute("url");String mapperClass = child.getStringAttribute("class");// 优先级 resource>url>mapperClassif (resource != null && url == null && mapperClass == null) {//如果mapper节点中的resource不为空ErrorContext.instance().resource(resource);// 那么直接加载resource指向的XXXMapper.xml文件为字节流InputStream inputStream = Resources.getResourceAsStream(resource);// 通过XMLMapperBuilder解析XXXMapper.xml,可以看到这里构建的XMLMapperBuilde还传入了configuration,所以之后肯定是会将mapper封装到configuration对象中去的。XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());// 解析mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {// 如果url!=null,那么通过url解析ErrorContext.instance().resource(url);InputStream inputStream = Resources.getUrlAsStream(url);XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());mapperParser.parse();} else if (resource == null && url == null && mapperClass != null) {// 如果mapperClass!=null,那么通过加载类构造ConfigurationClass<?> 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.");}}}}}
此方法中的入参:XNode parent就是上一步获取的root.evalNode(“mappers”),for循环遍历parent.getChildren()
<!-- 通过配置文件路径 --><mapper resource="mapper/UserMapper.xml"/><!-- 通过Java全限定类名 --><mapper class="com.mybatistest.TestMapper"/><!-- 通过url 通常是mapper不在本地时用 --><mapper url=""/><!-- 通过包名 --><package name="com.ibatis"/>
优先级别最高的就是package,其次就是resource,url,class,三者中只能有一个不能为空,否则将抛异常。
注意:mapper节点中,可以使用resource/url/class三种方式获取mapper
用resource来加载mapper.xml,通过mapperParser.parse()方法看一下它的解析过程。
public void parse() {//判断文件是否之前解析过if (!configuration.isResourceLoaded(resource)) {//解析mapper文件节点(主要)(下面贴了代码)configurationElement(parser.evalNode("/mapper"));configuration.addLoadedResource(resource);//绑定Namespace里面的Class对象bindMapperForNamespace();}//重新解析之前解析不了的节点,先不看,最后填坑。parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}
// 解析mapper文件里面的节点// 拿到里面配置的配置项 最终封装成一个MapperedStatemanetprivate void configurationElement(XNode context) {try {// 获取命名空间 namespace,这个很重要,后期mybatis会通过这个动态代理我们的Mapper接口String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {//如果namespace为空则抛一个异常throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 解析缓存节点cacheRefElement(context.evalNode("cache-ref"));cacheElement(context.evalNode("cache"));// 解析parameterMap(过时)和resultMap <resultMap></resultMap>parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));// 解析<sql>节点//<sql id="staticSql">select * from test</sql> (可重用的代码段)//<select> <include refid="staticSql"></select>sqlElement(context.evalNodes("/mapper/sql"));// 解析增删改查节点<select> <insert> <update> <delete>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);}
上述解析了各种节点,在XXXMapper.xml中必不可少的就是编写SQL,与数据库交互主要靠的也就是这个,所以着重说说解析增删改查节点的方法——buildStatementFromContext()。
在没贴代码之前,根据这个名字就可以略知一二了,这个方法会根据我们的增删改查节点,来构造一个Statement,而用过原生Jdbc的都知道,Statement就是我们操作数据库的对象。
private void buildStatementFromContext(List<XNode> list) {if (configuration.getDatabaseId() != null) {buildStatementFromContext(list, configuration.getDatabaseId());}//解析xmlbuildStatementFromContext(list, null);}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {// 解析xml节点statementParser.parseStatementNode();} catch (IncompleteElementException e) {// xml语句有问题时 存储到集合中 等解析完能解析的再重新解析configuration.addIncompleteStatement(statementParser);}}}
public void parseStatementNode() {// 获取<select id="xxx">中的idString id = context.getStringAttribute("id");//获取databaseId 用于多数据库,这里为nullString databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}//获取节点名 select update delete insertString nodeName = context.getNode().getNodeName();//根据节点名,得到SQL操作的类型SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));//判断是否是查询boolean isSelect = sqlCommandType == SqlCommandType.SELECT;//是否刷新缓存 默认:增删改刷新 查询不刷新boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);//是否使用二级缓存 默认值:查询使用 增删改不使用boolean useCache = context.getBooleanAttribute("useCache", isSelect);//是否需要处理嵌套查询结果 group by// 三组数据 分成一个嵌套的查询结果boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);// Include Fragments before parsingXMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);//替换Includes标签为对应的sql标签里面的值includeParser.applyIncludes(context.getNode());//获取parameterType名String parameterType = context.getStringAttribute("parameterType");//获取parameterType的ClassClass<?> parameterTypeClass = resolveClass(parameterType);//解析配置的自定义脚本语言驱动 这里为nullString lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them.//解析selectKeyprocessSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)//设置主键自增规则KeyGenerator keyGenerator;String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);if (configuration.hasKeyGenerator(keyStatementId)) {keyGenerator = configuration.getKeyGenerator(keyStatementId);} else {keyGenerator = context.getBooleanAttribute("useGeneratedKeys",configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;}/************************************************************************************///解析Sql(重要) 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);//获取StatementType,可以理解为Statement和PreparedStatementStatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));//没用过Integer fetchSize = context.getIntAttribute("fetchSize");//超时时间Integer timeout = context.getIntAttribute("timeout");//已过时String parameterMap = context.getStringAttribute("parameterMap");//获取返回值类型名String resultType = context.getStringAttribute("resultType");//获取返回值烈性的ClassClass<?> resultTypeClass = resolveClass(resultType);//获取resultMap的idString resultMap = context.getStringAttribute("resultMap");//获取结果集类型String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");// 将刚才获取到的属性,封装成MappedStatement对象(代码贴在下面)builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);}
第一步骤的总结:将xml中的各个节点(id,parameterType,resultType)进行解析,构造了一个MappedStatement对象,这个对象很复杂,MyBatis使用了构造者模式来构造这个对象,最后当MappedStatement对象构造完成后,将其封装到全局Configuration对象中,第一阶段结束。
<select id="selectUser" parameterType="java.lang.Integer" resultType="org.apache.ibatis.demo.User">select * from user where id= #{id}</select>
面试题:
解析mappers文件有几种方式?
4种
执行器有几种方式?
3中 简单的复用的 批量的
