《手写MyBatis框架全流程:从DOM4J解析到SQL执行原理剖析》
警示:该部分内容有难度,基础较弱的程序员可能有些部分是听不懂的,如果无法跟下来,可直接跳过,不影响后续知识点的学习。当然,如果你要能够跟下来,必然会让你加深对MyBatis框架的理解。
我们给自己的框架起个名:GodBatis(起名灵感来源于:my god!!! 我的天呢!)
一、dom4j解析XML文件
第一步:引入dom4j的依赖
<?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.group</groupId>
<artifactId>parse-xml-by-dom4j</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<dependencies>
<!--dom4j依赖-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!--jaxen依赖-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
</project>
第二步:编写配置文件godbatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
</dataSource>
</environment>
<mappers>
<mapper resource="sqlmapper.xml"/>
</mappers>
</environments>
</configuration>
第三步:解析godbatis-config.xml
@Test
public void testGodBatisConfig() throws Exception{
// 读取xml,获取document对象
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("godbatis-config.xml"));
// 获取<environments>标签的default属性的值
Element environmentsElt = (Element)document.selectSingleNode("/configuration/environments");
String defaultId = environmentsElt.attributeValue("default");
System.out.println(defaultId);
// 获取environment标签
Element environmentElt = (Element)document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
// 获取事务管理器类型
Element transactionManager = environmentElt.element("transactionManager");
String transactionManagerType = transactionManager.attributeValue("type");
System.out.println(transactionManagerType);
// 获取数据源类型
Element dataSource = environmentElt.element("dataSource");
String dataSourceType = dataSource.attributeValue("type");
System.out.println(dataSourceType);
// 将数据源信息封装到Map集合
Map<String,String> dataSourceMap = new HashMap<>();
dataSource.elements().forEach(propertyElt -> {
dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
});
dataSourceMap.forEach((k, v) -> System.out.println(k + ":" + v));
// 获取sqlmapper.xml文件的路径
Element mappersElt = (Element) document.selectSingleNode("/configuration/environments/mappers");
mappersElt.elements().forEach(mapper -> {
System.out.println(mapper.attributeValue("resource"));
});
}
运行结果:
第四步:编写配置文件sqlmapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="car">
<insert id="insertCar">
insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
</insert>
<select id="selectCarByCarNum" resultType="org.example1.pojo.Car">
select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where car_num = #{carNum}
</select>
</mapper>
第五步:解析sqlmapper.xml
@Test
public void testSqlMapper() throws Exception{
// 读取xml,获取document对象
SAXReader saxReader = new SAXReader();
Document document = saxReader.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("sqlmapper.xml"));
// 获取namespace
Element mapperElt = (Element) document.selectSingleNode("/mapper");
String namespace = mapperElt.attributeValue("namespace");
System.out.println(namespace);
// 获取sql id
mapperElt.elements().forEach(statementElt -> {
// 标签名
String name = statementElt.getName();
System.out.println("name:" + name);
// 如果是select标签,还要获取它的resultType
if ("select".equals(name)) {
String resultType = statementElt.attributeValue("resultType");
System.out.println("resultType:" + resultType);
}
// sql id
String id = statementElt.attributeValue("id");
System.out.println("sqlId:" + id);
// sql语句
String sql = statementElt.getTextTrim();
System.out.println("sql:" + sql);
});
}
二、GodBatis
手写框架之前,如果没有思路,可以先参考一下mybatis的客户端程序,通过客户端程序来逆推需要的类,参考代码:
@Test
public void testInsert(){
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 3.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执行SQL
Car car = new Car(null, "111", "宝马X7", "70.3", "2010-10-11", "燃油车");
int count = sqlSession.insert("insertCar",car);
System.out.println("更新了几条记录:" + count);
// 5.提交
sqlSession.commit();
} catch (Exception e) {
// 回滚
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 6.关闭
if (sqlSession != null) {
sqlSession.close();
}
}
}
@Test
public void testSelectOne(){
SqlSession sqlSession = null;
try {
// 1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
// 2.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
// 3.创建SqlSession对象
sqlSession = sqlSessionFactory.openSession();
// 4.执行SQL
Car car = (Car)sqlSession.selectOne("selectCarByCarNum", "111");
System.out.println(car);
// 5.提交
sqlSession.commit();
} catch (Exception e) {
// 回滚
if (sqlSession != null) {
sqlSession.rollback();
}
e.printStackTrace();
} finally {
// 6.关闭
if (sqlSession != null) {
sqlSession.close();
}
}
}
第一步:IDEA中创建模块,导入依赖
模块:godbatis(创建普通的Java Maven模块,打包方式jar),引入相关依赖
<?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.example1</groupId>
<artifactId>godbatis</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--依赖-->
<dependencies>
<!--dom4j依赖-->
<dependency>
<groupId>org.dom4j</groupId>
<artifactId>dom4j</artifactId>
<version>2.1.3</version>
</dependency>
<!--jaxen依赖-->
<dependency>
<groupId>jaxen</groupId>
<artifactId>jaxen</artifactId>
<version>1.2.0</version>
</dependency>
<!--junit依赖-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--mysql驱动依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
</project>
第二步:资源工具类,方便获取指向配置文件的输入流
package org.example1.utils;
import java.io.InputStream;
/**
* godbatis框架提供的一个工具类。
* 这个工具类专门完成“类路径”中资源的加载。
*/
public class Resources {
/**
* 工具类的构造方法都是建议私有化的。
* 因为工具类中的方法都是静态的,不需要创建对象就能调用。
* 为了避免new对象,所有构造方法私有化。
* 这只是一种编程习惯。
*/
private Resources(){}
/**
* 从类路径当中加载资源。
* @param resource 放在类路径当中的资源文件。
* @return 指向资源文件的一个输入流。
*/
public static InputStream getResourceAsStream(String resource){
return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
}
}
第三步:定义SqlSessionFactoryBuilder类
SqlSessionFactoryBuilder
主要用来创建SqlSessionFactory
实例。在 MyBatis 里,要想操作数据库,就必须先构建一个SqlSessionFactory
对象,而SqlSessionFactoryBuilder
就承担着这个构建任务。它可以从多种资源里解析配置信息,像 XML 文件、Java 对象等,然后根据这些配置信息创建SqlSessionFactory
实例。
提供一个无参数构造方法,再提供一个build方法,该build方法要返回SqlSessionFactory对象
package org.god.core;
import java.io.InputStream;
/**
* SqlSessionFactory对象构建器
*/
public class SqlSessionFactoryBuilder {
/**
* 创建构建器对象
*/
public SqlSessionFactoryBuilder() {
}
/**
* 获取SqlSessionFactory对象
* 该方法主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
* @param inputStream 指向核心配置文件的输入流
* @return SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream inputStream){
// 解析配置文件,创建数据源对象
// 解析配置文件,创建事务管理器对象
// 解析配置文件,获取所有的SQL映射对象
// 将以上信息封装到SqlSessionFactory对象中
// 返回
return null;
}
}
第四步:分析SqlSessionFactory类中有哪些属性
SqlSessionFactory
是一个工厂类,其主要作用是创建SqlSession
实例。SqlSession
是 MyBatis 里用于和数据库进行交互的核心对象,借助它能够执行 SQL 语句、管理事务等。SqlSessionFactory
负责管理数据库连接池、配置信息等,确保SqlSession
能够正确地和数据库进行交互。
1. 核心方法:openSession()
-
作用:创建并返回一个
SqlSession
对象(类似打开一个数据库连接,用于执行 SQL)。 -
代码逻辑:
public SqlSession openSession() { transaction.openConnection(); // 开启数据库连接 return new SqlSession(this); // 创建会话对象 }
2. 事务管理器的配置(setTransaction
/getTransaction
)
-
作用:动态设置或获取事务管理器(如 JDBC 事务、自定义事务)。
-
代码逻辑:
private Transaction transaction; // 面向接口编程,可灵活切换事务实现 public void setTransaction(Transaction transaction) { this.transaction = transaction; } public Transaction getTransaction() { return transaction; }
3. SQL 语句存储(setMappedStatements
/getMappedStatements
)
-
作用:存储所有映射的 SQL 语句(比如 XML 或注解中定义的 SQL)。
-
代码逻辑:
private Map<String, MappedStatement> mappedStatements; // key是SQL的ID public void setMappedStatements(Map<String, MappedStatement> mappedStatements) { this.mappedStatements = mappedStatements; } public Map<String, MappedStatement> getMappedStatements() { return mappedStatements; }
4. 构造方法(SqlSessionFactory
)
-
作用:初始化工厂时注入事务管理器和 SQL 配置。
-
你的代码逻辑:
public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) { this.transaction = transaction; this.mappedStatements = mappedStatements; }
第五步:定义JDBCTransaction
事务管理器最好是定义一个接口,然后每一个具体的事务管理器都实现这个接口。
package org.example1.core;
import java.sql.Connection;
/**
* 事务管理器接口。
* 所有的事务管理器都应该遵循该规范。
* JDBC事务管理器,MANAGED事务管理器都应该实现这个接口。
* Transaction事务管理器:提供管理事务方法。
*/
public interface Transaction {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
/**
* 真正的开启数据库连接。
*/
void openConnection();
/**
* 获取数据库连接对象的。
*/
Connection getConnection();
}
JdbcTransaction 实现类
package org.example1.core;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
/**
* JDBC事务管理器(godbatis框架目前只对JdbcTransaction进行实现。)
*/
public class JdbcTransaction implements Transaction{
/**
* 数据源属性
* 经典的设计:面向接口编程。
*/
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();
}
}
}
}
ManagedTransaction实现类
package org.example1.core;
import java.sql.Connection;
/**
* MANAGED事务管理器。(godbatis对这个类就不再实现了。)
*
*/
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;
}
}
第六步:事务管理器中需要数据源,定义UNPOOLEDDataSource
数据源是获取connection对象的
POOLED UNPOOLED JNDI
所有的数据源都要实现JDK的规范,javax.sql.DataSource
UnPooledDataSource类
package org.example1.core;
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对象。
*/
public class UnPooledDataSource implements javax.sql.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 {
Connection connection = DriverManager.getConnection(url, username, password);
return connection;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@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;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
PooledDataSource 类
package org.example1.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源的实现类:POOLED
* 使用godbatis框架内置的数据库连接池来获取Connection对象。(这个不实现了。)
*/
public class PooledDataSource implements javax.sql.DataSource{
@Override
public Connection getConnection() throws SQLException {
// 从数据库连接池当中获取Connection对象。(这个数据库连接池是我godbatis框架内部封装好的。)
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@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;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
JNDIDataSource类
package org.example1.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源的实现类:JNDI
* 使用第三方的数据库连接池获取Connection对象。(这个不实现了。)
*/
public class JNDIDataSource implements javax.sql.DataSource{
@Override
public Connection getConnection() throws SQLException {
return null;
}
@Override
public Connection getConnection(String username, String password) throws SQLException {
return null;
}
@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;
}
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return null;
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return false;
}
}
第七步:定义MappedStatement
package org.example1.core;
/**
* 普通的java类。POJO,封装了一个SQL标签。
* 一个MappedStatement对象对应一个SQL标签。
* 一个SQL标签中的所有信息封装到MappedStatement对象当中。
* 面向对象编程思想。
*/
public class MappedStatement {
/**
* sql语句
*/
private String sql;
/**
* 要封装的结果集类型。有的时候resultType是null。
* 比如:insert delete update语句的时候resultType是null。
* 只有当sql语句是select语句的时候resultType才有值。
*/
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() {
}
}
第八步:完善SqlSessionFactory类
package org.god.core;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
/**
* SqlSession工厂对象,使用SqlSessionFactory可以获取会话对象
*/
public class SqlSessionFactory {
private TransactionManager transactionManager;
private Map<String, GodMappedStatement> mappedStatements;
public SqlSessionFactory(TransactionManager transactionManager, Map<String, GodMappedStatement> mappedStatements) {
this.transactionManager = transactionManager;
this.mappedStatements = mappedStatements;
}
public TransactionManager getTransactionManager() {
return transactionManager;
}
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public Map<String, GodMappedStatement> getMappedStatements() {
return mappedStatements;
}
public void setMappedStatements(Map<String, GodMappedStatement> mappedStatements) {
this.mappedStatements = mappedStatements;
}
}
第九步:完善SqlSessionFactoryBuilder中的build方法
package org.example1.core;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.example1.utils.Resources;
import javax.sql.DataSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* SqlSessionFactory构建器对象。
* 通过SqlSessionFactoryBuilder的build方法来解析
* godbatis-config.xml文件,然后创建SqlSessionFactory对象。
*/
public class SqlSessionFactoryBuilder {
/**
* 无参数构造方法。
*/
public SqlSessionFactoryBuilder(){}
/**
* 解析godbatis-config.xml文件,来构建SqlSessionFactory对象。
* @param in 指向godbatis-config.xml文件的一个输入流。
* @return SqlSessionFactory对象。
*/
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 defaultId = environments.attributeValue("default");
Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
Element transactionElt = environment.element("transactionManager");
Element dataSourceElt = environment.element("dataSource");
List<String> sqlMapperXMLPathList = new ArrayList<>();
List<Node> nodes = document.selectNodes("//mapper"); // 获取整个配置文件中所有的mapper标签
nodes.forEach(node -> {
Element mapper = (Element) node;
String resource = mapper.attributeValue("resource");
sqlMapperXMLPathList.add(resource);
});
// 获取数据源对象
DataSource dataSource = getDataSource(dataSourceElt);
// 获取事务管理器
Transaction transaction = getTransaction(transactionElt,dataSource);
// 获取mappedStatements
Map<String, MappedStatement> mappedStatements = getMappedStatements(sqlMapperXMLPathList);
// 解析完成之后,构建SqlSessionFactory对象。
factory = new SqlSessionFactory(transaction, mappedStatements);
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
/**
* 解析所有的SqlMapper.xml文件,然后构建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");
// 这里进行了namespace和id的拼接,生成最终的sqlId
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 transactionElt 事务管理器标签元素
* @param dataSource 数据源对象
* @return
*/
private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
Transaction transaction = null;
String type = transactionElt.attributeValue("type").trim().toUpperCase();
if (Const.JDBC_TRANSACTION.equals(type)) {
transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。
}
if (Const.MANAGED_TRANSACTION.equals(type)) {
transaction = new ManagedTransaction();
}
return transaction;
}
/**
* 获取数据源对象
* @param dataSourceElt 数据源标签元素
* @return
*/
private DataSource getDataSource(Element dataSourceElt) {
Map<String,String> map = new HashMap<>();
// 获取所有的property
List<Element> propertyElts = dataSourceElt.elements("property");
propertyElts.forEach(propertyElt -> {
String name = propertyElt.attributeValue("name");
String value = propertyElt.attributeValue("value");
map.put(name, value);
});
DataSource dataSource = null;
String type = dataSourceElt.attributeValue("type").trim().toUpperCase();
if (Const.UN_POOLED_DATASOURCE.equals(type)) {
dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
}
if (Const.POOLED_DATASOURCE.equals(type)) {
dataSource = new PooledDataSource();//没有实现
}
if (Const.JNDI_DATASOURCE.equals(type)) {
dataSource = new JNDIDataSource();//没有实现
}
return dataSource;
}
}
第十步:在SqlSessionFactory中添加openSession方法
package org.example1.core;
import java.util.Map;
/**
* SqlSessionFactory对象:
* 一个数据库对应一个SqlSessionFactory对象。
* 通过SqlSessionFactory对象可以获取SqlSession对象。(开启会话)
* 一个SqlSessionFactory对象可以开启多个SqlSession会话。
*/
public class SqlSessionFactory {
/**
* 事务管理器属性
* 事务管理器是可以灵活切换的。
* 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;
}
/**
* 获取Sql会话对象。
* @return
*/
public SqlSession openSession(){
// 开启会话的前提是开启连接。(连接打开了)
transaction.openConnection();
// 创建SqlSession对象
SqlSession sqlSession = new SqlSession(this);
return sqlSession;
}
public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) {
this.transaction = transaction;
this.mappedStatements = mappedStatements;
}
public SqlSessionFactory() {
}
}
第十一步:编写SqlSession类
package org.example1.core;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
/**
* 专门负责执行SQL语句的会话对象
*/
public class SqlSession {
private SqlSessionFactory factory;
public SqlSession(SqlSessionFactory factory) {
this.factory = factory;
}
/**
* 执行insert语句,向数据库表当中插入记录。
* @param sqlId sql语句的id
* @param pojo 插入的数据。
* @return
*/
public int insert(String sqlId, Object pojo){
int count = 0;
try {
// JDBC代码,执行insert语句,完成插入操作。
Connection connection = factory.getTransaction().getConnection();
// insert into t_user values(#{id},#{name},#{age})
String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();
// insert into t_user(id,name,age) values(?,?,?)
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
PreparedStatement ps = connection.prepareStatement(sql);
// 给?占位符传值
// 难度是什么:
// 第一:你不知道有多少个?
// 第二:你不知道该将pojo对象中的哪个属性赋值给哪个 ?
// ps.String(第几个问号, 传什么值); // 这里都是setString,所以数据库表中的字段类型要求都是varchar才行。这是godbatis比较失败的地方。
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;
// 有属性名id,怎么获取id的属性值呢?调用getId()方法
String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);
Object propertyValue = getMethod.invoke(pojo);
ps.setString(index, propertyValue.toString());
index++;
}
// 执行SQL语句
count = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
return count;
}
/**
* 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句。
* @param sqlId
* @param param
* @return
*/
public Object selectOne(String sqlId, Object param){
Object obj = null;
try {
Connection connection = factory.getTransaction().getConnection();
MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);
// 这是那个DQL查询语句
// select * from t_user where id = #{id}
String godbatisSql = mappedStatement.getSql();
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
PreparedStatement ps = connection.prepareStatement(sql);
// 给占位符传值
ps.setString(1, param.toString());
// 查询返回结果集
ResultSet rs = ps.executeQuery();
// 要封装的结果类型。
String resultType = mappedStatement.getResultType(); // org.god.ibatis.pojo.User
// 从结果集中取数据,封装java对象
if (rs.next()) {
// 获取resultType的Class
Class<?> resultTypeClass = Class.forName(resultType);
// 调用无参数构造方法创建对象
obj = resultTypeClass.newInstance(); // Object obj = new User();
// 给User类的id,name,age属性赋值
// 给obj对象的哪个属性赋哪个值。
/*
mysql> select * from t_user where id = '1111';
+------+----------+------+
| id | name | age |
+------+----------+------+
| 1111 | zhangsan | 20 |
+------+----------+------+
解决问题的关键:将查询结果的列名作为属性名。
列名是id,那么属性名就是:id
列名是name,那么属性名就是:name
*/
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 0; i < columnCount; i++) {
String propertyName = rsmd.getColumnName(i + 1);
// 拼接方法名
String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
// 获取set方法
Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);
// 调用set方法给对象obj属性赋值
setMethod.invoke(obj, rs.getString(propertyName));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
// 局部测试
public static void main(String[] args) {
String sql = "insert into t_user values(#{id},#{name},#{age})";
int fromIndex = 0;
int index = 1;
while(true){
int jingIndex = sql.indexOf("#", fromIndex);
if (jingIndex < 0) {
break;
}
System.out.println(index);
index++;
int youKuoHaoIndex = sql.indexOf("}", fromIndex);
String propertyName = sql.substring(jingIndex + 2, youKuoHaoIndex).trim();
System.out.println(propertyName);
fromIndex = youKuoHaoIndex + 1;
}
}
/**
* 提交事务
*/
public void commit(){
factory.getTransaction().commit();
}
/**
* 回滚事务
*/
public void rollback(){
factory.getTransaction().rollback();
}
/**
* 关闭事务
*/
public void close(){
factory.getTransaction().close();
}
}
三、GodBatis使用Maven打包
查看本地仓库中是否已经有jar包:
四、使用GodBatis
使用GodBatis就和使用MyBatis是一样的。
第一步:准备数据库表t_user1
第二步:创建模块,普通的Java Maven模块:godbatis-test
第三步:引入依赖
<?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.example1</groupId>
<artifactId>godbatis-test</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!--依赖-->
<dependencies>
<!--godbatis依赖-->
<dependency>
<groupId>org.example1</groupId>
<artifactId>godbatis</artifactId>
<version>1.0</version>
</dependency>
<!--引入junit-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!--mysql-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.30</version>
</dependency>
</dependencies>
</project>
第四步:编写pojo类
package org.example1.pojo;
public class User {
private String id;
private String name;
private String age;
public User(String id, String name, String age) {
this.id = id;
this.name = name;
this.age = age;
}
public User() {
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
}
第五步:编写核心配置文件:godbatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<environments default="dev">
<environment id="dev">
<transactionManager type="JDBC"/>
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/jdbc"/>
<property name="username" value="root"/>
<property name="password" value="abc123"/>
</dataSource>
</environment>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</environments>
</configuration>
第六步:编写sql映射文件:UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="user">
<insert id="insertUser">
insert into t_user1 values(#{id},#{name},#{age})
</insert>
<select id="selectById" resultType="org.example1.pojo.User">
select * from t_user1 where id = #{id}
</select>
</mapper>
第七步:编写测试类
package oop;
import org.example1.core.SqlSession;
import org.example1.core.SqlSessionFactory;
import org.example1.core.SqlSessionFactoryBuilder;
import org.example1.pojo.User;
import org.example1.utils.Resources;
import org.junit.Test;
public class UserMapperTest {
@Test
public void testInsertUser(){
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行SQL
User user = new User("3333", "孙悟空", "5000");
int count = sqlSession.insert("user.insertUser", user);
System.out.println(count);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testSelectById(){
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
Object obj = sqlSession.selectOne("user.selectById", "3333");
System.out.println(obj);
sqlSession.close();
}
}
第八步:运行结果
五、总结MyBatis框架的重要实现原理
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="user">
<insert id="insertUser">
insert into t_user1 values(#{id},#{name},#{age})
</insert>
<select id="selectById" resultType="org.example1.pojo.User">
select * from t_user1 where id = #{id}
</select>
</mapper>
思考两个问题:
-
为什么insert语句中 #{} 里填写的必须是属性名?
-
为什么select语句查询结果列名要属性名一致?