警示:该部分内容有难度,基础较弱的程序员可能有些部分是听不懂的,如果无法跟下来,可直接跳过,不影响后续知识点的学习。当然,如果你要能够跟下来,必然会让你加深对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
@Testpublic 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"));// 获取namespaceElement mapperElt = (Element) document.selectSingleNode("/mapper");String namespace = mapperElt.attributeValue("namespace");System.out.println(namespace);// 获取sql idmapperElt.elements().forEach(statementElt -> {// 标签名String name = statementElt.getName();System.out.println("name:" + name);// 如果是select标签,还要获取它的resultTypeif ("select".equals(name)) {String resultType = statementElt.attributeValue("resultType");System.out.println("resultType:" + resultType);}// sql idString 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.执行SQLCar 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.执行SQLCar 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的IDpublic 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;@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();}}}
}
ManagedTransaction实现类
package org.example1.core;import java.sql.Connection;/*** MANAGED事务管理器。(godbatis对这个类就不再实现了。)**/
public class ManagedTransaction implements Transaction{@Overridepublic void commit() {}@Overridepublic void rollback() {}@Overridepublic void close() {}@Overridepublic void openConnection() {}@Overridepublic 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;}@Overridepublic Connection getConnection() throws SQLException {Connection connection = DriverManager.getConnection(url, username, password);return connection;}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@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;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic 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{@Overridepublic Connection getConnection() throws SQLException {// 从数据库连接池当中获取Connection对象。(这个数据库连接池是我godbatis框架内部封装好的。)return null;}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@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;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic 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{@Overridepublic Connection getConnection() throws SQLException {return null;}@Overridepublic Connection getConnection(String username, String password) throws SQLException {return null;}@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;}@Overridepublic <T> T unwrap(Class<T> iface) throws SQLException {return null;}@Overridepublic 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;@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() {}
}
第八步:完善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);// 获取mappedStatementsMap<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的拼接,生成最终的sqlIdString 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<>();// 获取所有的propertyList<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的ClassClass<?> 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() {}@Overridepublic 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 {@Testpublic void testInsertUser(){SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("godbatis-config.xml"));SqlSession sqlSession = sqlSessionFactory.openSession();// 执行SQLUser user = new User("3333", "孙悟空", "5000");int count = sqlSession.insert("user.insertUser", user);System.out.println(count);sqlSession.commit();sqlSession.close();}@Testpublic 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语句查询结果列名要属性名一致?