手写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.执行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();}}
}

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;}@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();}}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 {@Overridepublic void commit() {}@Overridepublic void rollback() {}@Overridepublic void close() {}@Overridepublic void openConnection() {}@Overridepublic 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;}@Overridepublic Connection getConnection() throws SQLException {// 这个连接池godbatis框架可以自己写一个连接池// 从数据库连接池当中获取Connection对象。(这个数据库练级吃是我godbatins框架内部封装好的。)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;}
}

在这里插入图片描述

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{@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;}
}

在这里插入图片描述

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{@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;}
}

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;}@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;}
}

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);// 获取mappedStatementsMap<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 的拼接,生成最终的sqlIdString 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<>();// 获取所有的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;//UNPOOLED POOLED JNDIString 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_\\$]*}", "?");// 执行sqlPreparedStatement 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_\\$]*}", "?");// 执行sqlPreparedStatement 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;}@Overridepublic 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 {@Testpublic void testInsertUser() {SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));SqlSession sqlSession = sqlSessionFactory.openSession();// 执行SQL insertUser user = new User("99999", "张三", "20");int count = sqlSession.insert("user.insert", user);sqlSession.commit();sqlSession.close();}@Testpublic 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. 总结:

  1. 大部分的框架的实现都是:反射机制 + 设计模式 + 注解 的方式实现的。
  2. 这里我们手写MyBatis 就仅仅只实现 insert() 和 selectOne(单条记录的查询)操作,并且有些局限就是,数据库的所有字段类型,都必须是 varchar 类型才行。
  3. 手写MyBatis 让我们更好的了解了 MyBatis 的运行机理,是如何通过反射机制读取相关信息进行设置的。

5. 最后:

“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/774594.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

win10 开启wsl

开启 hypver-v查看wsl poweershellwsl --list --online下载分发版本 等待下载即可 poweershellwsl --install -d Ubuntu-20.04设置为wsl2 poweershellwsl --set-default-version 2使用wsl powershell wslmoberxterm设置到wsl moberxterm设置清华源 /etc/apt/source.list # 默认…

如何在 Kubernetes 里部署 JMX Exporter

本文会通过一个 Java 应用,演示 Prometheus JMX Exporter 在 Kubernetes 里的部署和配置方式。为了更好地理解 JMX Exporter,我们将使用 Spring Boot Java 应用程序并将所有 JMX 指标导出给 Prometheus。在本指南结束时,您将学习:使用 Java 应用镜像启动 JMX Exporter 将 J…

vue3 双向绑定 dialog

父组件:<ViewPopupForm v-model="isView"/> 子组件: const emit = defineEmits([update:modelValue])const props = defineProps({isView: Boolean,//接收父组件ParentComponent传过来的数组 });

服务注册中心+配置中心-Nacos-微服务核心组件【分布式微服务笔记07】

服务注册中心+配置中心-Nacos-微服务核心组件【分布式微服务笔记07】 服务注册中心+配置中心-NacosNacos 有两大功能: 注册中心[替代Eureka]+配置中心[替代Config] 架构理论基础: CAP 理论(支持AP【高可用、分区容错性】 和CP【分区容错性和数据一致性】, 可以切换)Nacos 结构…

Oracle数据库自动备份

1.bat脚本 格式为ANSI格式 set CURDATE=%date:~0,4%%date:~5,2%%date:~8,2% set CURMON=%date:~0,4%%date:~5,2% set CURTIME=%time:~0,2% if "%CURTIME%"==" 0" set CURTIME=00 if "%CURTIME%"==" 1" set CURTIME=01 if "%CURT…

老式移动和联通标准SIM卡在2024的今天

前言 在我手里的,是一张普通联通和一张移动M-ZONE标准SIM卡桌上散落的SIM卡碎片已经说明了一切——— 我要把这些00后的SIM卡装入2024年的手机(图中为大卡的测试机,没那么有风险)联通 输入123456,直接被锁,提示要用puk码解锁我后来再搜索puk码的时候才知道默认pin码是123…

我的编程经历,从天桥地摊Basic到西藏阿里的.Net AOT。(一,从井到Sharp)

撇清一层歧义:标题中的阿里不是指阿里巴巴集团,喜马拉雅也不是指那个做音频频道的公司,文中所及内容以及我本人都与他们没有任何关联。依照地理正式名称:阿里指的是西藏西部阿里地区,喜马拉雅指的是青藏高原地球最高山脉。 从前我在博客园不叫这个名字,今天很多自己的早…

计算机网络基础第五讲 传输层

计算机网络基础第五讲 传输层 第一节:传输层概述 1. 运输层概述2. 运输层功能两种不同协议:TCP:面向连接,全双工可靠信道;仅支持单播;复杂 UDP:无连接不可靠;支持单播,多播,广播;应用层来负责可靠;简单 第二节:端口号 1. 运输层的端口 进程标识符来标记进程; 不应…

计算机网络基础第六讲 应用层

计算机网络基础第六讲 应用层 第一节:应用层概述第二节:DNS 1. 域名系统DNS实现域名到IP的映射2. 域名服务器3. 域名的解析过程迭代+递归解析递归解析4. 提高可靠性和速度的方法第三节:FTP 1.FTP概述2. FTP工作模式3. FTP两个连接控制连接必须先于数据连接建立 数据连接必须…

TCP状态转移图说明及使用tcpdump进行观测

一、TCP状态转移图说明图1.TCP状态转移图这张图展示了 TCP(Transmission Control Protocol,传输控制协议)的状态转移图,描述了 TCP 连接在不同阶段之间的状态变化和相互转换。 (一)、建立连接(三次握手)图2.TCP三次握手示意图1、服务器准备好接受外来连接,通常通过soc…

git学习笔记1

记录学习过程: git是如何运行工作的,先把文件从工作区传输到暂存区,之后再从暂存区传输到本地仓库git仓库的初始化,在需要的文件夹中右键鼠标"Open Git Bash Here",然后输入git init:git把文件先存放到暂存区之后git才能把文件存放到本地仓库可以查看当前git文件的…