一、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.xml
SAXReader 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” -- 获取整个配置文件中的 mapper
List<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);
// 获取 mappedStatements
Map<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 MANAGED
String 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<>();
// 获取所有 property
List<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 JNDI
String 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;
@Override
public Connection getConnection() {
return connection;
}
/**
* 创建事务管理器对象
* @param dataSource
* @param autoCommit
*/
public JdbcTransaction(DataSource dataSource, boolean autoCommit) {
this.dataSource = dataSource;
this.autoCommit = autoCommit;
}
@Override
public void commit() {
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void rollback() {
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public void close() {
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
@Override
public 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{
@Override
public void commit() {
}
@Override
public void rollback() {
}
@Override
public void close() {
}
@Override
public void openConnection() {
}
@Override
public 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;
}
@Override
public Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,username,password);
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public 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
*/
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public 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 {
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
@Override
public PrintWriter getLogWriter() throws SQLException {
return null;
}
@Override
public void setLogWriter(PrintWriter out) throws SQLException {
}
@Override
public void setLoginTimeout(int seconds) throws SQLException {
}
@Override
public int getLoginTimeout() throws SQLException {
return 0;
}
@Override
public 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;
@Override
public 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 的 class
Class<?> 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 语句的解析与执行
一 叶 知 秋,奥 妙 玄 心