一、dom4j 解析 XML 文件
在 dom4j 中,DOMReader
和 SAXReader
是两种不同的 XML 解析器。
它们的主要区别在于解析 XML 的方式和所提供的功能:
-
DOMReader:
-
DOMReader
使用 DOM(Document Object Model)模型来表示整个 XML 文档,将整个 XML 文档加载到内存中,以树形结构的方式表示整个文档。 -
优点:可以随机访问和修改文档中的任何部分,方便对文档进行增删改查操作。
-
缺点:由于将整个文档加载到内存中,对于大型 XML 文档会占用较多的内存,可能导致性能问题。
-
-
SAXReader:
-
SAXReader
使用 SAX(Simple API for XML)解析器,采用事件驱动的方式逐行读取和解析 XML 文档,不需要将整个文档加载到内存中。 -
优点:适合处理大型 XML 文档,因为不需要一次性加载整个文档,可以减少内存占用。
-
缺点:相对于 DOM 模型,SAX 模型不支持随机访问和修改文档的能力,只能顺序读取文档内容并响应特定事件。
-
选择使用 DOMReader
还是 SAXReader
取决于具体的需求。
如果需要频繁地对文档进行修改或随机访问,适合使用 DOMReader
。
而如果处理大型文档或只需顺序读取文档内容,那么 SAXReader
是更好的选择。
-
解析核心配置文件
// 创建 SAXReader 对象
SAXReader saxReader = new SAXReader();
// 通过 ClassLoader 加载 xml 文件
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
// 读 xml 文件,返回 document 对象,document 对象是文档对象,代表整个 xml 文件
Document document = saxReader.read(is);// 获取文档中的根标签
/*Element rootElement = document.getRootElement();String rootElementName = rootElement.getName();System.out.println("根结点:" + rootElementName);
*/// 获取 default 环境 id
// xpath 是做标签路径匹配的,能够快速定位 xml 中的元素
// 从根下找 configuration 标签,然后找 configuration 下的 environments 标签
String xpath = "/configuration/environments";
// Element 是 Node 的子类,方法更多,使用更便捷
Element environments = (Element) document.selectSingleNode(xpath);
// System.out.println(environments);
// 获取属性值
String defaultEnvironmentId = environments.attributeValue("default");
// System.out.println("默认环境id :" + defaultEnvironmentId);// 获取具体环境
xpath = "//configuration/environments/environment[@id='" + defaultEnvironmentId + "']";
Element environment = (Element) document.selectSingleNode(xpath);
// System.out.println(environment);// 获取 environment 下的 transactionManager 结点
// element - 获取孩子结点
Element transactionManager = environment.element("transactionManager");
String transactionManagerType = transactionManager.attributeValue("type");
System.out.println("transactionManagerType : " + transactionManagerType);// 获取 dataSource 结点
Element dataSource = environment.element("dataSource");
String dataSourceType = dataSource.attributeValue("type");
System.out.println("dataSourceType : " + dataSourceType);// 获取 dataSource 下的所有子节点
List<Element> propertyEles = dataSource.elements();
// 遍历
propertyEles.forEach(propertyEle -> {String name = propertyEle.attributeValue("name");String value = propertyEle.attributeValue("value");System.out.println(name + " : " + value);
});// 获取所有 mapper 标签
// 不想从根下开始,想从任意位置开始获取所有标签需要这样写
xpath = "//mapper";
List<Node> mappers = document.selectNodes(xpath);
// 遍历
mappers.forEach(mapper -> {Element mapperEle= (Element) mapper;String resource = mapperEle.attributeValue("resource");System.out.println(resource);
});
- 解析 SqlMapper 文件
SAXReader saxReader = new SAXReader();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");
Document document = saxReader.read(is);// 获取 namespace
String xpath = "/mapper";
Element mapper = (Element) document.selectSingleNode(xpath);
String namespace = mapper.attributeValue("namespace");
System.out.println(namespace);// 获取 mapper 下的所有子节点
List<Element> elements = mapper.elements();
elements.forEach(element -> {String id = element.attributeValue("id");// 没有该属性的 Sql 语句则会返回一个 “null”String resultType = element.attributeValue("resultType");System.out.println("id : " + id + ",resultType : " + resultType);// 获取 Sql 语句(获取标签中的文本内容,去除前后空白)String sql = element.getTextTrim();System.out.println(sql);/*** MyBatis 封装了 JDBC,需要执行的是带 ? 的 SQL 语句,所以需要将以下 SQL 语句做转化* insert into t_car * values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})** insert into t_car values(null,?,?,?,?,?)*/String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");System.out.println(newSql);
}
二、手写 GodBatis
Jaxen 是一个用于 Java 平台的开源 XPath 库,它提供了在 XML 文档中执行 XPath 查询的功能。
Jaxen 的目标是提供一个简单、易用且高效的方式来解析和查询 XML 文档,使开发人员能够轻松地使用 XPath 表达式来定位和提取 XML 文档中的数据。
一些 Jaxen 库的特点包括:
-
支持标准的 XPath 语法:Jaxen 遵循标准的 XPath 语法规范,可以执行常见的 XPath 查询操作,如按路径查找节点、筛选节点、使用谓词等。
-
跨平台性:作为一个 Java 库,Jaxen 可以在不同的 Java 平台上运行,提供了对 XML 文档的跨平台查询能力。
-
易于集成:Jaxen 提供了简洁的 API,使得开发人员可以轻松地将 XPath 功能集成到 Java 应用程序中。
-
灵活性:Jaxen 支持不同类型的 XML 文档,如 DOM、SAX、JDOM 等,使得开发人员可以根据需求选择合适的 XML 解析器来进行 XPath 查询。
总的来说,Jaxen 是一个方便、灵活且功能丰富的 Java XPath 库,适用于需要在 Java 应用程序中对 XML 文档进行复杂查询和处理的场景。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>org.god.ibatis</groupId><artifactId>godBatis</artifactId><version>1.0</version><properties><maven.compiler.source>8</maven.compiler.source><maven.compiler.target>8</maven.compiler.target></properties><dependencies><!--dom4j--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.1</version></dependency><!--jexen--><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.2.0</version></dependency><!--junit--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--mysql--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.30</version></dependency></dependencies></project>
Resources工具类
package org.god.ibatis.utils;import java.io.InputStream;/*** 工具类:加载类路径中资源** @author 秋玄* @version 1.0* @package org.god.ibatis.utils* @date 2022-09-26-07:50* @since 1.0*/
public class Resources {/*** 工具类构造方法都是私有的* 因为工具类中的方法都是静态的,不需要创建对象就可以调用*/private Resources() {}/*** 从类路径中加载资源* @param resource 类路径中的资源文件* @return 指向资源文件的输入流*/public static InputStream getResourceAsStream(String resource){return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);}
}
SqlSessioniFactoryBuilder
package org.god.ibatis.core;import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.god.ibatis.utils.Resources;import javax.sql.DataSource;
import java.io.InputStream;
import java.util.*;/*** SqlSessionFactory 构建器对象* 通过 SqlSessioniFactoryBuilder 的 build 方法解析* godbatis-config.xml 文件,创建 SqlSessionFactory 对象** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-07:55* @since 1.0*/
public class SqlSessioniFactoryBuilder {/*** 无参数构造方法*/public SqlSessioniFactoryBuilder() {}/*** 解析 godbatis-config.xml 文件,构建 SqlSesionFactory 对象* @param in 指向 godbatis-config.xml 文件的一个输入流* @return SqlSesionFactory 对象*/public SqlSessionFactory build(InputStream in){SqlSessionFactory factory = null;try {// 解析核心配置文件 godbatis-config.xmlSAXReader reader = new SAXReader();Document document = reader.read(in);Element environments = (Element) document.selectSingleNode("/configuration/environments");String dafaultId = environments.attributeValue("default");Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='"+ dafaultId +"']");Element transactionEle = environment.element("transactionManager");Element dataSourceEle = environment.element("dataSource");List<String> sqlMapperXMLPathList = new ArrayList<>();// “//mapper” -- 获取整个配置文件中的 mapperList<Node> nodes = document.selectNodes("//mapper");nodes.forEach(node -> {Element mapper = (Element) node;String resource = mapper.attributeValue("resource");sqlMapperXMLPathList.add(resource);});// 获取数据源DataSource dataSource = getDataSource(dataSourceEle);// 获取事务管理器Transaction transaction = getTransaction(transactionEle,dataSource);// 获取 mappedStatementsMap<String,MappedStatement> mappedStatements = getMappedStatements(sqlMapperXMLPathList);// 构建 SqlSessionFactory 对象factory = new SqlSessionFactory(transaction,mappedStatements);} catch (Exception e) {e.printStackTrace();}return factory;}/*** 解析所有的 SqlMapper 文件,构建 Map 集合* @param sqlMapperXMLPathList* @return*/private Map<String, MappedStatement> getMappedStatements(List<String> sqlMapperXMLPathList) {Map<String,MappedStatement> mappedStatements = new HashMap<>();sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {try {SAXReader reader = new SAXReader();Document document = reader.read(Resources.getResourceAsStream(sqlMapperXMLPath));Element mapper = (Element) document.selectSingleNode("mapper");String namespace = mapper.attributeValue("namespace");List<Element> elements = mapper.elements();elements.forEach(element -> {String id = element.attributeValue("id");String sqlId = namespace + "." + id;String resultType = element.attributeValue("resultType");String sql = element.getTextTrim();MappedStatement mappedStatement = new MappedStatement(sql, resultType);mappedStatements.put(sqlId,mappedStatement);});} catch (Exception e) {e.printStackTrace();}});return mappedStatements;}/*** 获取事务管理器* @param transactionEle 事务管理器标签元素* @param dataSource 数据源对象* @return 事务管理器标签元素对应的事务管理器对象*/private Transaction getTransaction(Element transactionEle, DataSource dataSource) {Transaction transaction = null;// type 可能的值:JDBC MANAGEDString type = transactionEle.attributeValue("type").trim().toUpperCase();switch (type){case Const.JDBC_TRANSACTION:/* false:默认开启事务,需要手动提交 */transaction = new JdbcTransaction(dataSource,false);break;case Const.MANAGED_TRANSACTION:transaction = new ManagedTransaction();break;}return transaction;}/*** 获取数据源* @param dataSourceEle 数据源标签元素* @return 数据源标签元素对应的数据源对象*/private DataSource getDataSource(Element dataSourceEle) {Map<String,String> map = new HashMap<>();// 获取所有 propertyList<Element> propertys = dataSourceEle.elements("property");propertys.forEach(propertyEle -> {String name = propertyEle.attributeValue("name");String value = propertyEle.attributeValue("value");map.put(name,value);});DataSource dataSource = null;// type 可能的值:UNPOOLED POOLED JNDIString type = dataSourceEle.attributeValue("type").trim().toUpperCase();switch (type){case Const.UN_POOLED_DATASOURCE:dataSource = new UnPooledDataSource(map.get("driver"),map.get("url"),map.get("username"), map.get("password"));break;case Const.POOLED_DATASOURCE:dataSource = new PooledDataSource();break;case Const.JNDI_DATASOURCE:dataSource = new JndiDataSource();break;}return dataSource;}
}
SqlSessionFactory
package org.god.ibatis.core;import java.util.Map;/*** 一个数据库一般对应一个 SqlSessionFactory 对象* 通过 SqlSessionFactory 对象可以获取 SqlSession 对象(开启会话)* 一个 SqlSessionFactory 对象可以开启多个 SqlSession 会话** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-08:01* @since 1.0*/
public class SqlSessionFactory {/*** 事务管理器* 事务管理器是可以灵活切换的* SqlSessionFactory 类中的事务管理器应该是面向接口编程*/private Transaction transaction;/*** 存放 SQL 语句的 Map 集合* key 是 sqlId* value 是对应的 SQL 标签信息对象*/private Map<String,MappedStatement> mappedStatements;public Transaction getTransaction() {return transaction;}public void setTransaction(Transaction transaction) {this.transaction = transaction;}public Map<String, MappedStatement> getMappedStatements() {return mappedStatements;}public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {this.mappedStatements = mappedStatements;}/*** 获取 SqlSession 对象* @return SqlSession 对象*/public SqlSession openSession(){// 开启连接transaction.openConnection();// 创建 SqlSession 对象/*this 指的是当前的 SqlSessionFactory 对象它包含了 transaction 和 mappedStatements同时对外提供了 getter 方法*/SqlSession sqlSession = new SqlSession(this);return sqlSession;}public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) {this.transaction = transaction;this.mappedStatements = mappedStatements;}public SqlSessionFactory() {}
}
全局常量
package org.god.ibatis.core;/*** 整个框架的常量类** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-27-07:15* @since 1.0*/
public class Const {public static final String UN_POOLED_DATASOURCE = "UNPOOLED";public static final String POOLED_DATASOURCE = "POOLED";public static final String JNDI_DATASOURCE = "JNDI";public static final String JDBC_TRANSACTION = "JDBC";public static final String MANAGED_TRANSACTION = "MANAGED";
}
Transaction 接口
package org.god.ibatis.core;import java.sql.Connection;/*** 事务管理器接口* 所有的事务管理器都应该遵循此规范* JDBC 事务管理器* MANAGED 事务管理器* 事务管理器:提供控制事务的方法** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-17:58* @since 1.0*/
public interface Transaction {/*** 提交事务*/void commit();/*** 回滚事务*/void rollback();/*** 关闭事务*/void close();/*** 开启数据库连接*/void openConnection();/*** 获取数据库连接对象*/Connection getConnection();
}
Transaction 接口实现类
package org.god.ibatis.core;import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;/*** JDBC 事务管理器** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-18:03* @since 1.0*/
public class JdbcTransaction implements Transaction{/*** 数据源属性* 所有的数据源都要实现 JDK 自带的规范:javax.sql.DataSource*/private DataSource dataSource;/*** 自动提交标志* true:自动提交* false:不自动提交*/private boolean autoCommit;/*** 连接对象*/private Connection connection;@Overridepublic Connection getConnection() {return connection;}/*** 创建事务管理器对象* @param dataSource* @param autoCommit*/public JdbcTransaction(DataSource dataSource, boolean autoCommit) {this.dataSource = dataSource;this.autoCommit = autoCommit;}@Overridepublic void commit() {try {connection.commit();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void rollback() {try {connection.rollback();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void close() {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}@Overridepublic void openConnection(){if (connection == null) {try {connection = dataSource.getConnection();// 开启事务connection.setAutoCommit(autoCommit);} catch (SQLException e) {e.printStackTrace();}}}
}
package org.god.ibatis.core;import java.sql.Connection;/*** MANAGED 事务管理器(不实现)** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-18:04* @since 1.0*/
public class ManagedTransaction implements Transaction{@Overridepublic void commit() {}@Overridepublic void rollback() {}@Overridepublic void close() {}@Overridepublic void openConnection() {}@Overridepublic Connection getConnection() {return null;}
}
数据源
package org.god.ibatis.core;import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;/*** 数据源实现类 UNPOOLED* 不使用连接池,每次都新建 Connection 对象** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-18:27* @since 1.0*/
public class UnPooledDataSource implements DataSource {private String url;private String username;private String password;/*** 创建一个数据源对象* @param driver* @param url* @param username* @param password*/public UnPooledDataSource(String driver, String url, String username, String password) {try {// 直接注册驱动Class.forName(driver);} catch (ClassNotFoundException e) {throw new RuntimeException(e);}this.url = url;this.username = username;this.password = password;}@Overridepublic Connection getConnection() throws SQLException {return DriverManager.getConnection(url,username,password);}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class<?> iface) throws SQLException {return false;}@Overridepublic PrintWriter getLogWriter() throws SQLException {return null;}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {}@Overridepublic int getLoginTimeout() throws SQLException {return 0;}@Overridepublic Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}
}
package org.god.ibatis.core;import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;/*** 数据源实现类:POOLED* 使用 godbatis 框架内置的数据库连接池来获取 Connection 对象(不实现)** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-18:28* @since 1.0*/
public class PooledDataSource implements DataSource {/*** 从数据连接池中获取 Connection 对象* 这个数据库连接池 godbatis 框架可以自己写一个连接池* @return* @throws SQLException*/@Overridepublic Connection getConnection() throws SQLException {return null;}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class<?> iface) throws SQLException {return false;}@Overridepublic PrintWriter getLogWriter() throws SQLException {return null;}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {}@Overridepublic int getLoginTimeout() throws SQLException {return 0;}@Overridepublic Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}
}
package org.god.ibatis.core;import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;/*** 数据源的实现:JNDI* 使用第三方的数据库连接池获取 Connection 对象(不实现)** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-18:28* @since 1.0*/
public class JndiDataSource implements DataSource {@Overridepublic Connection getConnection() throws SQLException {return null;}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic boolean isWrapperFor(Class<?> iface) throws SQLException {return false;}@Overridepublic PrintWriter getLogWriter() throws SQLException {return null;}@Overridepublic void setLogWriter(PrintWriter out) throws SQLException {}@Overridepublic void setLoginTimeout(int seconds) throws SQLException {}@Overridepublic int getLoginTimeout() throws SQLException {return 0;}@Overridepublic Logger getParentLogger() throws SQLFeatureNotSupportedException {return null;}
}
封装 SQL 标签
package org.god.ibatis.core;/*** 普通 Java 类,用于封装一个 SQL 标签* 一个 MappedStatement 对象对应一个 SQL 标签** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-26-08:21* @since 1.0*/
public class MappedStatement {/*** sql 语句*/private String sql;/*** 要封装的结果集类型* 当 sql 语句是 select 语句时 resultType 才有值* 其他情况都是 null*/private String resultType;@Overridepublic String toString() {return "MappedStatement{" +"sql='" + sql + '\'' +", resultType='" + resultType + '\'' +'}';}public String getSql() {return sql;}public void setSql(String sql) {this.sql = sql;}public String getResultType() {return resultType;}public void setResultType(String resultType) {this.resultType = resultType;}public MappedStatement(String sql, String resultType) {this.sql = sql;this.resultType = resultType;}public MappedStatement() {}
}
执行 SQL 语句
package org.god.ibatis.core;import java.lang.reflect.Method;
import java.sql.*;/*** 执行 SQL 语句的会话对象** @author 秋玄* @version 1.0* @package org.god.ibatis.core* @date 2022-09-27-08:29* @since 1.0*/
public class SqlSession {private SqlSessionFactory factory;public SqlSession(SqlSessionFactory factory) {this.factory = factory;}/*** 执行 insert 语句,向数据库表中插入记录* @param id sql 语句的 id* @param pojo 插入的数据* @return 插入记录的数量*/public int insert(String id,Object pojo){int count = 0;try {Connection connection = factory.getTransaction().getConnection();// insert into t_user values(#{id},#{name},#{age});String godBatisSql = factory.getMappedStatements().get(id).getSql();String sql = godBatisSql.replaceAll("#\\{[0-9A-Za-z_$]*}","?");PreparedStatement ps = connection.prepareStatement(sql);// 给占位符传值(局限性:这里都是 setString,所以要求数据库表中的字段类型都是 varchar 类型)// 将 pojo 的属性与占位符对应// 获取占位符的数量int fromIndex = 0;// 问号下标int index = 1;while (true){// # 的下标int jingIndex = godBatisSql.indexOf("#",fromIndex);// 找不到 # 结束循环if(jingIndex < 0){break;}// } 的下标,# 与 } 中间的字符串包含了一个属性名int youKuoHaoIndex = godBatisSql.indexOf("}",fromIndex);String propertyName = godBatisSql.substring(jingIndex + 2,youKuoHaoIndex).trim();fromIndex = youKuoHaoIndex + 1;String getter = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);Method getMethod = pojo.getClass().getDeclaredMethod(getter);Object propertyValue = getMethod.invoke(pojo);ps.setString(index,propertyValue.toString());index++;}count = ps.executeUpdate();} catch (Exception e) {e.printStackTrace();}return count;}/*** 执行查询语句,返回一个对象* 只适合返回一条记录的 sql 语句* @param id* @param param* @return*/public Object selectOne(String id,Object param){Object obj = null;try {Connection connection = factory.getTransaction().getConnection();String giodBatisSql = factory.getMappedStatements().get(id).getSql();String sql = giodBatisSql.replaceAll("#\\{[0-9A-Za-z_$]*}","?");PreparedStatement ps = connection.prepareStatement(sql);// 这里只对一个占位符的情况做处理ps.setString(1,param.toString());ResultSet resultSet = ps.executeQuery();String resultType = factory.getMappedStatements().get(id).getResultType();// 从结果集取数据封装对象if (resultSet.next()) {// 获取 resultType 的 classClass<?> clazz = Class.forName(resultType);// 调用无参数构造方法创建对象obj = clazz.newInstance();// 给属性赋值// 关键:将查询结果的列名作为属性名ResultSetMetaData metaData = resultSet.getMetaData();int columnCount = metaData.getColumnCount();for (int i = 1; i < columnCount + 1; i++) {String propertyName = metaData.getColumnName(i);// 拼接方法名String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);// 获取 set 方法Method setMethod = clazz.getDeclaredMethod(setMethodName, String.class);// 调用 set 方法给对象 obj 赋值setMethod.invoke(obj,resultSet.getString(propertyName));}}} catch (Exception e) {e.printStackTrace();}return obj;}/*** 提交事务*/public void commint(){factory.getTransaction().commit();}/*** 回滚事务*/public void rollback(){factory.getTransaction().rollback();}/*** 关闭事务*/public void close(){factory.getTransaction().close();}
}
打包到本地仓库
使用 GodBatis 框架
<dependency><groupId>org.god.ibatis</groupId><artifactId>godBatis</artifactId><version>1.0</version>
</dependency>
三、思路
Mybatis 核心流程:
(采用对 MyBatis 框架使用的过程逆推的方式)
① 首先需要创建一个 SqlSessionFactoryBuilder 对象
-
通过 SqlSessionFactoryBuilder 对象的 build 方法解析核心配置文件,构建 SqlSessionFactory 对象(需要一个 InputStream 流对象指向核心配置文件)
-
封装一个工具类 Resources 用于加载类路径中的资源
-
其中的方法都是静态的,不需要对象就可以调用,所以工具类构造方法一般私有化,避免创建对象
-
-
解析核心配置文件,创建具体的事务管理器对象、数据源对象以及存放 SQL 语句的 Map 集合。调用 SqlSessionFactory 的构造方法,传入事务管理器、Map 集合创建 SqlSessionFactory 对象
② 构建 SqlSessionFactory 对象
-
一个数据库一般对应一个 SqlSessionFactory 对象
-
通过 SqlSessionFactory 对象可以获取 SqlSession 对象(开启会话)
-
一个 SqlSessionFactory 对象可以开启多个 SqlSession 会话
属性分析
在 SqlSessionFactoryBuilder 对象的 build 方法中需要构建一个 SqlSessionFactory 对象,并对其各个属性赋值,再将其作为返回值返回。
这个 SqlSessionFactory 对象需要封装的数据应该是 “事务管理器”、“存放 SQL 语句的 Map 集合”(使用 MappedStatement 类封装,具有 sql 及 resultType 两个属性)
由于事务管理器具有数据源属性,所以 SqlSessionFactory 对象可以通过其事务管理器属性获取数据源,故自身不需要数据源属性了,避免冗余
事务管理器
由于用户需要的事务管理器可能是 JDBC 事务管理器、MANAGED 事务管理器,所以这里采用面向接口编程的思想,抽取事务管理器接口,然后各种具体的事务管理器再对其方法做具体实现;使用时根据用户设置的属性值判断再创建具体的子类对象
子类需要实现的方法有:
-
提交事务
-
回滚事务
-
关闭事务
-
及其他方法(此处暂时未知,后续再做补充)
-
开启数据库连接(后续添加的)
-
获取数据库链接对象(后续添加的)
子类在具体实现接口方法时,要实现对事务的控制,则需要调用 Connectioin 对象的方法,而 Connection 对象需要通过数据源创建,所以 Transaction 对象需要有一个数据源的属性(此时因为事务管理器对象包含了数据源对象,所以 SqlSessionFactory 不需要有数据源属性)
为了保证执行事务的是同一个连接对象,所以给事务管理器对象添加一个 Connection 属性,并添加 openConnection、getConnection 方法,获取数据源中的连接对象
数据源
数据源与事务管理器类似,采用面向接口编程思想,实现 javax.sql.DataSource 接口,实现类的动态创建
在数据源对象中完成注册驱动、获取 Connection 连接对象,同时设置一个是否自动提交事务的标记
注册驱动只需要注册一次即可,所以在构造方法中完成
Connection 则在每一个调用 getConnection 方法时创建一个(UNPOOLED,不使用数据库连接池的实现)
③ 封装 SqlSession 对象,完成配置文件中 SQL 语句的解析与执行
一 叶 知 秋,奥 妙 玄 心