手写MyBatis 重要基本原理框架
1. 手写MyBatis 重要基本原理框架
@
- 1. 手写MyBatis 重要基本原理框架
- 1.1 第一步:IDEA中创建模块
- 1.2 第二步:资源工具类,方便获取指向配置文件的输入流
- 1.3 第三步:定义SqlSessionFactoryBuilder类
- 1.4 第四步:分析SqlSessionFactory类中有哪些属性
- 1.5 第五步:定义JDBCTransaction
- 1.6 第六步:事务管理器中需要数据源,定义UNPOOLEDDataSource
- 1.7 第七步:定义一个MappedStatement 类用于存放 SQL 标签
- 1.8 第八步:完善SqlSessionFactory类
- 1.9 第九步:完善SqlSessionFactoryBuilder中的build方法
- 1.10 第十步:编写SqlSession类中commit rollback close方法
- 1.11 第十一步:编写SqlSession类中的insert方法
- 1.12 第十二步:编写SqlSession类中的selectOne方法
- 2. 将我们自己手写的MyBatiks 名为“godbatis”的框架,使用Maven打包
- 3. 使用我们自己手写的 MyBatis 名为 “godbatis” 的框架,运行测试
- 4. 总结:
- 5. 最后:
这里我们手写MyBatis 就仅仅只实现 insert() 和 selectOne(单条记录的查询)操作,并且有些局限就是,数据库的所有字段类型,都必须是 varchar 类型才行。
手写框架之前,如果没有思路,可以先参考一下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();
}
}
}
1.1 第一步: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.god.ibatis</groupId>
<artifactId>godbatis</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</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>
<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>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
1.2 第二步:资源工具类,方便获取指向配置文件的输入流
和MyBatis 框架一样,这里我们创建一个 Resoures 工具类,用来获取配置文件的输入流对象。
工具类的构造方法都是建议私有化的
因为工具类中的方法都是静态的,不需要创建对象就能调用
为了避免new对象,所有构造方法私有化
这只是一种编程习惯
package org.god.ibatis.utils;
import java.io.InputStream;
/**
* godbatis 框架提供的一个工具类
* 这个工具类专门完成“类路径” 中资源的加载
*/
public class Resources {
/**
* 工具类的构造方法都是建议私有化的
* 因为工具类中的方法都是静态的,不需要创建对象就能调用
* 为了避免new对象,所有构造方法私有化
* 这只是一种编程习惯
*/
private Resources() {}
/**
* 从类路径当中加载资源
* @param resource 放在类路径当中的资源文件
* @return 指向资源文件的一个输入流
*/
public static InputStream getResourceAsStream(String resource) {
InputStream resourceAsStream = ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
return resourceAsStream;
}
}
1.3 第三步:定义SqlSessionFactoryBuilder类
提供一个无参数构造方法,再提供一个build方法,该build方法要返回SqlSessionFactory对象
package org.god.core;
import java.io.InputStream;
public class SqlSessionFactoryBuilder {
/**
* 创建构建器对象
*/
public SqlSessionFactoryBuilder() {
}
/**
* 获取SqlSessionFactory对象
* 该方法主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
* @param inputStream 指向核心配置文件的输入流
* @return SqlSessionFactory对象
*/
public SqlSessionFactory build(InputStream inputStream){
// 解析配置文件,创建数据源对象
// 解析配置文件,创建事务管理器对象
// 解析配置文件,获取所有的SQL映射对象
// 将以上信息封装到SqlSessionFactory对象中
// 返回
return null;
}
}
1.4 第四步:分析SqlSessionFactory类中有哪些属性
-
事务管理器
-
- GodJDBCTransaction
-
SQL映射对象集合
-
- Map<String, GodMappedStatement>
1.5 第五步:定义JDBCTransaction
事务管理器最好是定义一个接口,然后每一个具体的事务管理器都实现这个接口。
package org.god.ibatis.core;
import java.sql.Connection;
/**
* 事务管理接口
* 所有的事务管理器都应该遵循该规范
* JDBC 事务管理器,MANAGED 事务管理器都应该实现这个接口
* Transaction事务管理器,提供管理事务方法。
*/
public interface Transaction {
/**
* 提交事务
*/
void commit();
/**
* 回滚事务
*/
void rollback();
/**
* 关闭事务
*/
void close();
/**
* 真正的开启数据库连接
*/
void openConnection();
/**
* 获取数据库连接对象的
*/
Connection getConnection();
}
关于MyBatis 的事务,在 MyBatis 中有两种类型的事务管理器(也就是 type="[JDBC|MANAGED]"):,但是这里我们只实现JDBC,这个值的事务。其他另外一个就不实现了。
package org.god.ibatis.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;
public Connection getConnection() {
return connection;
}
public void setConnection(Connection connection) {
this.connection = 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();
}
}
public void openConnection() {
if (connection == null) {
try {
this.connection = dataSource.getConnection();
connection.setAutoCommit(autoCommit);
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
package org.god.ibatis.core;
import java.sql.Connection;
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;
}
}
1.6 第六步:事务管理器中需要数据源,定义UNPOOLEDDataSource
在MyBatis中有三种内建的数据源类型(也就是 type="[UNPOOLED|POOLED|JNDI]"),这里我们就实现UNPOOLED 这个值,另外两个值,就不实现了。
数据源是获取connection对象的
POOlED UNPOOLED JNDI
所有的数据源都要实现 JDK带的规范,javax.sql.DataSource
package org.god.ibatis.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 driver;
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.driver = driver;
this.url = url;
this.username = username;
this.password = password;
}
@Override
public Connection getConnection() throws SQLException {
// 这个连接池godbatis框架可以自己写一个连接池
// 从数据库连接池当中获取Connection对象。(这个数据库练级吃是我godbatins框架内部封装好的。)
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;
}
}
package org.god.ibatis.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 {
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;
}
}
package org.god.ibatis.core;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;
/**
* 数据源的实现类:JNDI
* 使用godbatis 框架内置的数据库连接池来获取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;
}
}
1.7 第七步:定义一个MappedStatement 类用于存放 SQL 标签
- 普通的Java类,POJO,封装了一个SQL标签
- 一个MappedStatement 对象对应一个SQL标签
- 一个SQL标签中的所有信息封装到MappedStatement对象当中
- 面向对象编程思想
package org.god.ibatis.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;
public MappedStatement(String sql, String resultType) {
this.sql = sql;
this.resultType = 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;
}
}
1.8 第八步:完善SqlSessionFactory类
SqlSessionFactory对象;
- 一个数据库对应一个SqlSessionFactory对象
- 通过SqlSessionFactory对象可以获取SqlSession对象(开启会话)
- 一个SqlSessionFactory 对象可以开启对哦个SqlSession 会话
在SqlSessionFactory中添加openSession方法
package org.god.ibatis.core;
import java.util.List;
import java.util.Map;
/**
* SqlSessionFactory对象;
* 一个数据库对应一个SqlSessionFactory对象
* 通过SqlSessionFactory对象可以获取SqlSession对象(开启会话)
* 一个SqlSessionFactory 对象可以开启对哦个SqlSession 会话
*/
public class SqlSessionFactory {
/**
* 获取Sql会话对象
* @return
*/
public SqlSession openSession() {
// 开启会话的前提是开启连接
transaction.openConnection();
// 创建SqlSession 对象
SqlSession sqlSession = new SqlSession(this);
return sqlSession;
}
public SqlSessionFactory() {
}
/**
* 事务管理属性
* 事务管理器可以灵活切换的
* SqlSessionFactory类中的事务管理器应该是面向接口编程的
* SqlSessionFactory类中的应该有一个事务管理器接口
*/
private Transaction transaction;
/**
* 数据源属性
*/
/**
* 存放SqL语句的Map集合
* key 是 sqlid
* value 是对应的 SQL标签信息对象
*/
private Map<String, MappedStatement> mappedStatements;
public Map<String, MappedStatement> getMappedStatements() {
return mappedStatements;
}
public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
this.mappedStatements = this.getMappedStatements();
}
public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatement) {
this.transaction = transaction;
this.mappedStatements = mappedStatement;
}
public Transaction getTransaction() {
return transaction;
}
public void setTransaction(Transaction transaction) {
this.transaction = transaction;
}
}
1.9 第九步:完善SqlSessionFactoryBuilder中的build方法
这里我们定义一个常量类,用于方便后续的读取:,提高代码的可读性。
package org.god.ibatis.core;
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";
}
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.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;
}
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");
System.out.println(resultType);
String sql = element.getTextTrim();
System.out.println(sql);
MappedStatement mappedStatement = new MappedStatement(sql, resultType);
mappedStatements.put(sqlId,mappedStatement);
});
} catch (DocumentException e) {
e.printStackTrace();
}
});
return mappedStatements;
}
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;
//UNPOOLED POOLED JNDI
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;
}
}
1.10 第十步:编写SqlSession类中commit rollback close方法
package org.god.ibatis.core;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
public class SqlSession {
private SqlSessionFactory factory;
public SqlSession(SqlSessionFactory factory) {
this.factory = factory;
}
/**
* 提交事务
*/
public void commit() {
factory.getTransaction().commit();
}
/**
* 回滚事务
*/
public void rollback() {
factory.getTransaction().rollback();
}
/**
* 关闭事务
*/
public void close() {
factory.getTransaction().close();
}
}
这里我们手写MyBatis 就仅仅只实现 insert() 和 selectOne(单条记录的查询)操作,并且有些局限就是,数据库的所有字段类型,都必须是 varchar 类型才行。
1.11 第十一步:编写SqlSession类中的insert方法
package org.god.ibatis.core;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
public class SqlSession {
private SqlSessionFactory factory;
public SqlSession(SqlSessionFactory factory) {
this.factory = factory;
}
/**
* 执行insert语句,向数据库表当中插入记录
*
* @param
* @return
*/
public int insert(String sqlId, Object pojo) {
int count = 0;
try {
// JDBC代码,执行insert 语句 ,完成插入操作
Connection connection = factory.getTransaction().getConnection();
// insert into t_car values(#{id},#{name},#{age})
String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();
System.out.println(factory.getMappedStatements().get(sqlId));
System.out.println(factory.getMappedStatements().get(sqlId).getSql());
// insert into t_user values(?,?,?)
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
PreparedStatement ps = connection.prepareStatement(sql);
// 给 ? 占位符传值
// 难度是什么?
// 第一: 你不知道有多少个?
// 第二:你不知道该将Pojo对象中的那个属性赋值给哪个?
// ps.String(第几个问号,传什么值); // 这里都是setString,所以数据库中的字段类型要求都是varchar才行,
// 这是godbatis 比较失败的地方
int formIndex = 0;
int index = 1;
while (true) {
int jingIndex = godbatisSql.indexOf("#", formIndex);
if (jingIndex < 0) {
break;
}
System.out.println(index);
int youKuoHaoIndex = godbatisSql.indexOf("}", formIndex);
String propertyName = godbatisSql.substring(jingIndex + 2, youKuoHaoIndex).trim();
System.out.println(propertyName);
formIndex = 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++;
}
count = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
// 给?占位符传值
return count;
}
/**
* 查询一个对象
* @param sqlId
* @param parameterObj
* @return
*/
public Object selectOne(String sqlId, Object parameterObj){
MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);
Connection connection = factory.getTransaction().getConnection();
// 获取sql语句
String godbatisSql = mappedStatement.getSql();
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
// 执行sql
PreparedStatement ps = null;
ResultSet rs = null;
Object obj = null;
try {
ps = connection.prepareStatement(sql);
ps.setString(1, parameterObj.toString());
rs = ps.executeQuery();
if (rs.next()) {
// 将结果集封装对象,通过反射
String resultType = mappedStatement.getResultType();
Class<?> aClass = Class.forName(resultType);
obj = aClass.newInstance();
// 给对象obj属性赋值
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnName(i);
String setMethodName = "set" + columnName.toUpperCase().charAt(0) + columnName.substring(1);
Method setMethod = aClass.getDeclaredMethod(setMethodName, aClass.getDeclaredField(columnName).getType());
setMethod.invoke(obj, rs.getString(columnName));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return obj;
}
/**
* 提交事务
*/
public void commit() {
factory.getTransaction().commit();
}
/**
* 回滚事务
*/
public void rollback() {
factory.getTransaction().rollback();
}
/**
* 关闭事务
*/
public void close() {
factory.getTransaction().close();
}
}
1.12 第十二步:编写SqlSession类中的selectOne方法
package org.god.ibatis.core;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
public class SqlSession {
private SqlSessionFactory factory;
public SqlSession(SqlSessionFactory factory) {
this.factory = factory;
}
/**
* 执行insert语句,向数据库表当中插入记录
*
* @param
* @return
*/
public int insert(String sqlId, Object pojo) {
int count = 0;
try {
// JDBC代码,执行insert 语句 ,完成插入操作
Connection connection = factory.getTransaction().getConnection();
// insert into t_car values(#{id},#{name},#{age})
String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();
System.out.println(factory.getMappedStatements().get(sqlId));
System.out.println(factory.getMappedStatements().get(sqlId).getSql());
// insert into t_user values(?,?,?)
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
PreparedStatement ps = connection.prepareStatement(sql);
// 给 ? 占位符传值
// 难度是什么?
// 第一: 你不知道有多少个?
// 第二:你不知道该将Pojo对象中的那个属性赋值给哪个?
// ps.String(第几个问号,传什么值); // 这里都是setString,所以数据库中的字段类型要求都是varchar才行,
// 这是godbatis 比较失败的地方
int formIndex = 0;
int index = 1;
while (true) {
int jingIndex = godbatisSql.indexOf("#", formIndex);
if (jingIndex < 0) {
break;
}
System.out.println(index);
int youKuoHaoIndex = godbatisSql.indexOf("}", formIndex);
String propertyName = godbatisSql.substring(jingIndex + 2, youKuoHaoIndex).trim();
System.out.println(propertyName);
formIndex = 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++;
}
count = ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}
// 给?占位符传值
return count;
}
/**
* 查询一个对象
* @param sqlId
* @param parameterObj
* @return
*/
public Object selectOne(String sqlId, Object parameterObj){
MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);
Connection connection = factory.getTransaction().getConnection();
// 获取sql语句
String godbatisSql = mappedStatement.getSql();
String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
// 执行sql
PreparedStatement ps = null;
ResultSet rs = null;
Object obj = null;
try {
ps = connection.prepareStatement(sql);
ps.setString(1, parameterObj.toString());
rs = ps.executeQuery();
if (rs.next()) {
// 将结果集封装对象,通过反射
String resultType = mappedStatement.getResultType();
Class<?> aClass = Class.forName(resultType);
obj = aClass.newInstance();
// 给对象obj属性赋值
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = rsmd.getColumnName(i);
String setMethodName = "set" + columnName.toUpperCase().charAt(0) + columnName.substring(1);
Method setMethod = aClass.getDeclaredMethod(setMethodName, aClass.getDeclaredField(columnName).getType());
setMethod.invoke(obj, rs.getString(columnName));
}
}
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
try {
ps.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return obj;
}
/**
* 执行查询语句,返回一个对象,该方法只适合返回一条记录的sql语句
* @param sqlId
* @param param
* @return
*/
public Object selectOne2(String sqlId,Object param) {
Object obj = null;
try {
Connection connection = factory.getTransaction().getConnection();
MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);
// 这是哪个DQL查询语句
// select id,name,age 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();
// 从结果集中取数据,封装Java对象
if(rs.next()) {
// 获取 resultType 的 Class对象
Class<?> resultTypeClass = Class.forName(resultType);
// 调用无参数构造方法创建对象
obj = resultTypeClass.newInstance(); // Object obj = new User();
// 给User类的id,name,age 属性赋值
// 给Obj 对象的哪个属性赋哪个值
/*
解决问题的关键:将查询结果的列名作为属性名
列名是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,resultTypeClass.getDeclaredField(propertyName).getType());
Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName,String.class);
// 调用set 方法给对象Obj属性赋值
setMethod.invoke(obj,rs.getString(propertyName));
}
}
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
/**
* 提交事务
*/
public void commit() {
factory.getTransaction().commit();
}
/**
* 回滚事务
*/
public void rollback() {
factory.getTransaction().rollback();
}
/**
* 关闭事务
*/
public void close() {
factory.getTransaction().close();
}
}
2. 将我们自己手写的MyBatiks 名为“godbatis”的框架,使用Maven打包
根据所提示的放置的路径的位置,查看本地仓库中是否已经有jar包:
3. 使用我们自己手写的 MyBatis 名为 “godbatis” 的框架,运行测试
使用godbatis 就和使用MyBatis是一样的。
第一步:准备数据库表t_user。这里我们的手写的MyBatis 框架有所局限,缺陷,只能识别字段类型为 varchar 的数据,所以我们这里的数据表的,字段的类型都定义为了 varchar 类型的。
表数据:
第二步:创建模块,普通的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>com.rainbowsea</groupId>
<artifactId>godbatis-test</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.god.ibatis</groupId>
<artifactId>godbatis</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
第四步:编写pojo类
package com.rainbowsea.godbatis.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;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", age='" + age + '\'' +
'}';
}
public User() {
}
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="mybatis">
<environment id="mybatis">
<!-- MANAGED 没有用第三框架管理的话,都是会被提交的,没有事务上的管理了。-->
<transactionManager type="JDBC"/>
<!-- 数据源是获取connection对象的 -->
<!-- POOlED UNPOOLED JNDI -->
<!-- 所有的数据源都要实现 JDK带的规范,javax.sql.DataSource-->
<dataSource type="UNPOOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="MySQL123"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="sqlMapper.xml"></mapper>
</mappers>
</configuration>
第六步:编写sql映射文件:sqlMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!--namespace 一定要是:对应的接口的全限定类名-->
<mapper namespace="user">
<!-- id 要是 namespace 对应接口上的方法名: -->
<insert id="insert">
insert into t_user values(#{id},#{name},#{age})
</insert>
<select id="selectAll" resultType="org.god.ibatis.pojo.User">
select id,name,age from t_user where id = #{id}
</select>
</mapper>
第七步:编写测试类
package com.rainbowsea.godbatis.test;
import org.god.ibatis.core.SqlSession;
import org.god.ibatis.core.SqlSessionFactory;
import org.god.ibatis.core.SqlSessionFactoryBuilder;
import org.god.ibatis.pojo.User;
import org.god.ibatis.utils.Resources;
import org.junit.Test;
public class UserMapperTest {
@Test
public void testInsertUser() {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行SQL insert
User user = new User("99999", "张三", "20");
int count = sqlSession.insert("user.insert", user);
sqlSession.commit();
sqlSession.close();
}
@Test
public void testSelectOne() {
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
SqlSession sqlSession = sqlSessionFactory.openSession();
// 执行SQL语句
Object obj = sqlSession.selectOne2("user.selectAll", "666");
System.out.println(obj);
sqlSession.close();
}
}
4. 总结:
- 大部分的框架的实现都是:反射机制 + 设计模式 + 注解 的方式实现的。
- 这里我们手写MyBatis 就仅仅只实现 insert() 和 selectOne(单条记录的查询)操作,并且有些局限就是,数据库的所有字段类型,都必须是 varchar 类型才行。
- 手写MyBatis 让我们更好的了解了 MyBatis 的运行机理,是如何通过反射机制读取相关信息进行设置的。
5. 最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”