本文主要是介绍手写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. 总结:
- 大部分的框架的实现都是:反射机制 + 设计模式 + 注解 的方式实现的。
- 这里我们手写MyBatis 就仅仅只实现 insert() 和 selectOne(单条记录的查询)操作,并且有些局限就是,数据库的所有字段类型,都必须是 varchar 类型才行。
- 手写MyBatis 让我们更好的了解了 MyBatis 的运行机理,是如何通过反射机制读取相关信息进行设置的。
5. 最后:
“在这个最后的篇章中,我要表达我对每一位读者的感激之情。你们的关注和回复是我创作的动力源泉,我从你们身上吸取了无尽的灵感与勇气。我会将你们的鼓励留在心底,继续在其他的领域奋斗。感谢你们,我们总会在某个时刻再次相遇。”
这篇关于手写MyBatis 重要基本原理框架的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!