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 configuration
PUBLIC "-//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);
// 构建sqkSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建sqlSession
SqlSession 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.");
}
// 如果没有解析过,则继续往下解析,并且将标识符置为true
parsed = true;
// 解析<configuration>节点
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
沿着这个流程可以看到返回了一个configration的类,这个类就是我们的xml中的所有属性转换为对象
进入parseConfiguration(),主要关注下mappers节点;
private void parseConfiguration(XNode root) {
try {
// 解析<Configuration>下的节点
// issue #117 read properties first
propertiesElement(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 #631
environmentsElement(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>mapperClass
if (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,那么通过加载类构造Configuration
Class<?> 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文件里面的节点
// 拿到里面配置的配置项 最终封装成一个MapperedStatemanet
private 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());
}
//解析xml
buildStatementFromContext(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">中的id
String id = context.getStringAttribute("id");
//获取databaseId 用于多数据库,这里为null
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//获取节点名 select update delete insert
String 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 parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
//替换Includes标签为对应的sql标签里面的值
includeParser.applyIncludes(context.getNode());
//获取parameterType名
String parameterType = context.getStringAttribute("parameterType");
//获取parameterType的Class
Class<?> parameterTypeClass = resolveClass(parameterType);
//解析配置的自定义脚本语言驱动 这里为null
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
//解析selectKey
processSelectKeyNodes(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和PreparedStatement
StatementType 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");
//获取返回值烈性的Class
Class<?> resultTypeClass = resolveClass(resultType);
//获取resultMap的id
String 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中 简单的复用的 批量的