1、准备工作
引入dom4j依赖,引入mysql驱动依赖
注意:因为是手写mybatis框架,所以并没有引入mybatis的相关依赖
<?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.powernode</groupId> <artifactId>parse-xml-by-dom4j</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> </properties> <dependencies> <!--dom4j依赖--> <dependency> <groupId>org.dom4j</groupId> <artifactId>dom4j</artifactId> <version>2.1.3</version> </dependency> <dependency> <groupId>jaxen</groupId> <artifactId>jaxen</artifactId> <version>1.2.0</version> </dependency> <!--junit测试类--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!--数据库驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency> </dependencies> </project>
二、手写Mybatis思路
完全是按照mybatis查询数据库时,所需要的步骤,来创建对象,以及解析。 这是原生的mybatis查询代码:
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream ins = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(ins); SqlSession sqlSession = sqlSessionFactory.openSession(); int insertCar = sqlSession.insert("insertCar"); System.out.println(insertCar);
接下来我们会手动去创建这些对象,来实现查询的过程
三、正式开始
1、创建工具类 Resources
作用:从类路径下加载配置文件
public class Resources { private Resources(){} public static InputStream getResourceAsStream(String resource){ return ClassLoader.getSystemClassLoader().getResourceAsStream(resource); } }
关于工具类的几点说明: 1、工具类的构造方法 都是建议私有化 2、因为工具类中的方法都是静态的,不需要创建对象来调用 3、为了避免调用,所有的构造方法都是私有化
2、创建SqlSessionFactoryBuilder类
作用: 通过SqlSessionFactoryBuilder的build方法来解析 mybatis-config.xml文件, 从而获取SqlSessionFactory对象,以及数据源对象DataSource, 事务管理器对象Transaction,MappedStatement对象,即sql标签信息
package org.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.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方法来解析 mybatis-config.xml文件, * 然后 构建SqlSessionFactory对象 */ public class SqlSessionFactoryBuilder { public SqlSessionFactoryBuilder(){} //通过SqlSessionFactoryBuilder的build方法来解析 mybatis-config.xml文件 // 然后 创建SqlSessionFactory对象 public SqlSessionFactory build(InputStream inputStream){ SqlSessionFactory factory = null; //解析mybatis-config.xml文件 SAXReader saxReader = new SAXReader(); Document document = null; try { document = saxReader.read(inputStream); } catch (DocumentException e) { e.printStackTrace(); } String xPath = "/configuration/environments"; Element environments = (Element) document.selectSingleNode(xPath); String defaultId = environments.attributeValue("default"); xPath = "/configuration/environments/environment[@id='"+defaultId+"']"; Element environment = (Element) document.selectSingleNode(xPath); Element transactionManagerElement= environment.element("transactionManager"); Element dataSourceElement = environment.element("dataSource"); List<String> sqlMapperXmlPaths = new ArrayList<>(); List<Node> nodes = document.selectNodes("//mapper"); nodes.forEach(n -> { Element e = (Element) n; String resource = e.attributeValue("resource"); sqlMapperXmlPaths.add(resource); }); //获取数据源对象 DataSource dataSource = getDataSource(dataSourceElement); //获取事务管理器对象,事务管理器需要数据源对象 Transaction transaction = getTransaction(transactionManagerElement,dataSource); //获取MappedStatement对象,即sql标签信息 Map<String, MappedStatement> mappedStatement = getMappedStatement(sqlMapperXmlPaths); //解析完成之后,构建SqlSessionFactory对象 factory = new SqlSessionFactory(transaction, mappedStatement); return factory; } /** * 解析所有的sqlMapper.xml文件,然后构建map集合 * * @param sqlMapperXmlPaths * @return */ private Map<String, MappedStatement> getMappedStatement(List<String> sqlMapperXmlPaths) { Map<String, MappedStatement> mappedStatementMap = new HashMap<>(); sqlMapperXmlPaths.forEach(sqlMapperXmlPath ->{ //根据文件路径,去解析xml文件 try { SAXReader saxReader = new SAXReader(); InputStream ins = Resources.getResourceAsStream(sqlMapperXmlPath); Document document = saxReader.read(ins); Element mapper = (Element) document.selectSingleNode("mapper"); String namespace = mapper.attributeValue("namespace"); List<Element> elements = mapper.elements(); elements.forEach(e ->{ String id = e.attributeValue("id"); String sqlId = namespace + "." + id;//生成唯一的sqlId,因为namespace是不重复的 String resultType = e.attributeValue("resultType"); String sql = e.getTextTrim(); MappedStatement statement = new MappedStatement(sql,resultType); mappedStatementMap.put(sqlId,statement); }); } catch (DocumentException e) { e.printStackTrace(); } }); return mappedStatementMap; } /** * 获取数据源对象 * @param dataSourceElement * @return */ private DataSource getDataSource(Element dataSourceElement) { Map<String,String> map = new HashMap<>(); DataSource dataSource = null; List<Element> propertyElements = dataSourceElement.elements("property"); propertyElements.forEach(p ->{ String name = p.attributeValue("name"); String value = p.attributeValue("value"); map.put(name,value); }); //UNPOOLED POOLED JNDI String type = dataSourceElement.attributeValue("type").trim().toUpperCase(); if(Const.UNPOOLED_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; } /** * 获取事务管理器对象 * @param transactionManagerElement * @param dataSource * @return */ private Transaction getTransaction(Element transactionManagerElement,DataSource dataSource) { Transaction transaction = null; String type = transactionManagerElement.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; } }
3、创建SqlSessionFactory类
一个数据库,对应一个SqlSessionFactory对象 通过SqlSessionFactory对象获取SqlSession对象,开启会话 一个SqlSessionFactory 对象可以开启多个SqlSession会话
package org.ibatis.core; import java.util.Map; /** * 一个数据库,对应一个SqlSessionFactory对象 * 通过SqlSessionFactory对象获取SqlSession对象,开启会话 * 一个SqlSessionFactory 对象可以开启多个SqlSession会话 */ public class SqlSessionFactory { /** * 属性: * 1、事务管理器:因为事务管理器是可以灵活切换的,所以应该是面向接口编程 * 2、存放SQL语句的map集合,key为 sqlId,value为sql标签信息对象 */ //事务管理器 private Transaction transaction; // 存放SQL标签信息的map集合 private Map<String,MappedStatement> mappedStatements; public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) { this.transaction = transaction; this.mappedStatements = mappedStatements; } public Transaction getTransaction() { return transaction; } public void setTransaction(Transaction transaction) { this.transaction = transaction; } public Map<String, MappedStatement> getMappedStatements() { return mappedStatements; } public void setMappedStatements(Map<String, MappedStatement> mappedStatements) { this.mappedStatements = mappedStatements; } /** * 获取sql会话对象 * @return */ public SqlSession OpenSession(){ //开启会话前,先开启连接 transaction.openConnection(); //创建sqlSession对象 SqlSession sqlSession = new SqlSession(this); return sqlSession; } }
4、创建事务管理器接口:Transaction
事务管理器接口: 所有的事务管理器都应该实现该接口 JDBC事务管理器,MANAGED事务管理器 都要实现这个接口 事务管理器,提供管理事务的方法
package org.ibatis.core; import java.sql.Connection; import java.sql.SQLException; /** * 事务管理器接口: * 所有的事务管理器都应该实现该接口 * JDBC事务管理器,MANAGED事务管理器 都要实现这个接口 * 事务管理器,提供管理事务的方法 */ public interface Transaction { void commit() throws SQLException; void rollback(); void close(); void openConnection();//开启数据库连接对象 Connection getConnection();//获取数据库连接对象 }
5、创建事务管理器接口实现类:JdbcTransaction
package org.ibatis.core; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; /** * JDBC 事务管理器 */ public class JdbcTransaction implements Transaction { private DataSource dataSource; private boolean autoCommit;//自动提交标志:(true 自动提交 , false 不采用自动提交,也就是使用事务管理) private Connection connection; 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() { } @Override public void openConnection() { if (connection == null) { try { connection = dataSource.getConnection(); connection.setAutoCommit(autoCommit); } catch (SQLException e) { e.printStackTrace(); } } } @Override public Connection getConnection() { return connection; } }
6、创建事务管理器接口实现类:ManagedTransaction
package org.ibatis.core; import java.sql.Connection; /** * MANAGED 事务管理器 */ 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; } }
7、三种实现数据源DataSource接口的实现类
数据源实现类:PooledDataSource
package org.ibatis.core; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; /** * 数据源实现类:PooledDataSource * 使用连接池对象 */ public class PooledDataSource implements DataSource { @Override public Connection getConnection() throws SQLException { return null; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @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; } }
数据源实现类:UnPooledDataSource
package org.ibatis.core; import javax.sql.DataSource; 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 DataSource { private String url; private String username; private String password; public UnPooledDataSource(String driver, String url, String username, String password) { try { //注册驱动 Class.forName(driver); } catch (ClassNotFoundException e) { e.printStackTrace(); } this.url = url; this.username = username; this.password = password; } @Override public Connection getConnection() throws SQLException { return DriverManager.getConnection(url,username,password); } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @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; } }
数据源实现类:JNDIDataSource
package org.ibatis.core; import javax.sql.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.logging.Logger; /** * 数据源实现类:JNDI * 使用第三方的数据库连接池,获取Connection对象 */ public class JNDIDataSource implements DataSource { @Override public Connection getConnection() throws SQLException { return null; } @Override public Connection getConnection(String username, String password) throws SQLException { return null; } @Override public <T> T unwrap(Class<T> iface) throws SQLException { return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } @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; } }
8、创建MappedStatement类,存放sql标签中的所有信息
package org.ibatis.core; /** * 一个SQL标签中的所有信息封装到MappedStatement对象中 */ public class MappedStatement { private String sql;//sql语句 private String resultType;//结果集类型,insert语句没有该值,select语句才有值 public MappedStatement() { } public MappedStatement(String sql, String resultType) { this.sql = sql; this.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; } }
9、创建执行sql的会话对象(核心类)
package org.ibatis.core; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.*; import java.util.Map; /** * 执行sql语句的会话对象 */ public class SqlSession { private SqlSessionFactory factory; public SqlSession(SqlSessionFactory factory) { this.factory = factory; } //insert、 public int insert(String sqlId,Object object) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { Map<String, MappedStatement> mappedStatements = factory.getMappedStatements(); MappedStatement mappedStatement = mappedStatements.get(sqlId); Connection connection = factory.getTransaction().getConnection(); try { String originSql = mappedStatement.getSql(); String sql = originSql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");//转换为? PreparedStatement preparedStatement = connection.prepareStatement(sql); //给sql语句的? 占位符传值 int fromIndex = 0; int index = 1; while (true){ int i = originSql.indexOf("#",fromIndex); if(i<0){ break; } int rightIndex = originSql.indexOf("}",fromIndex); String propertyName = originSql.substring(i + 2, rightIndex).trim(); fromIndex = rightIndex + 1; String getMethodName = "get"+propertyName.toUpperCase().charAt(0)+propertyName.substring(1); Method declaredMethod = object.getClass().getDeclaredMethod(getMethodName); Object propertyValue = declaredMethod.invoke(object); preparedStatement.setString(index,propertyValue.toString()); index ++; } return preparedStatement.executeUpdate(); } catch (SQLException e) { e.printStackTrace(); } return 0; } // selectOne方法,返回单个对象 public Object selectOne(String sqlId,Object param){ Object object = null; try { Connection connection = factory.getTransaction().getConnection(); MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId); //dql查询语句 //select * from t_user where id = #{id} String originSql = mappedStatement.getSql(); String sql = originSql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");//# 转换为 ? PreparedStatement preparedStatement = connection.prepareStatement(sql); //给占位符传值(暂时不做复杂的,就只有一个占位符) preparedStatement.setString(1,param.toString()); ResultSet rs = preparedStatement.executeQuery(); String resultType = mappedStatement.getResultType(); //从结果集中封装java对象 if (rs.next()) { Class<?> resultTypeClass = Class.forName(resultType); object = resultTypeClass.newInstance();//相当于 Object object = new User(); //如何给Object的属性赋值 ResultSetMetaData metaData = rs.getMetaData(); int columnCount = metaData.getColumnCount();//多少列 for (int i = 0; i < columnCount; i++) { //column the first column is 1, the second is 2, ... String columnName = metaData.getColumnName(i+1); String setMethodName = "set"+columnName.toUpperCase().charAt(0)+columnName.substring(1); Method method = resultTypeClass.getDeclaredMethod(setMethodName, String.class); //调用set方法给对象object属性赋值 method.invoke(object,rs.getString(columnName)); } } } catch (Exception e) { e.printStackTrace(); } return object; } public void commit(){ try { factory.getTransaction().commit(); } catch (SQLException e) { e.printStackTrace(); } } public void rollback(){ factory.getTransaction().rollback(); } public void close(){ factory.getTransaction().close(); } public static void main(String[] args) { String sql = "insert into t_car values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})"; int fromIndex = 0; int index = 1; while (true){ int i = sql.indexOf("#",fromIndex); if(i<0){ break; } int rightIndex = sql.indexOf("}",fromIndex); String propertyName = sql.substring(i + 2, rightIndex).trim(); fromIndex = rightIndex + 1; index ++; } } }
10、常量类Const
package org.ibatis.core; public class Const { /** * 数据源连接池类型 */ public static final String UNPOOLED_DATASOURCE = "UNPOOLED"; public static final String POOLED_DATASOURCE = "POOLED"; public static final String JNDI_DATASOURCE = "NDI"; /** * 事务管理器类型 */ public static final String JDBC_TRANSACTION = "JDBC"; public static final String MANAGED_TRANSACTION = "MANAGED"; }
四、测试工作
1、准备 pojo实体类User
package org.ibatis.pojo; public class User { private String id; private String name; private String age; public User(){} public User(String id, String name, String age) { this.id = id; this.name = name; this.age = age; } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getAge() { return age; } public void setAge(String age) { this.age = age; } @Override public String toString() { return "User{" + "id='" + id + '\'' + ", name='" + name + '\'' + ", age='" + age + '\'' + '}'; } }
2、准备 mybatis-config.xml文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="UNPOOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/powernode?useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> <environment id="mybatisDB"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatisDB?useUnicode=true&characterEncoding=utf-8"/> <property name="username" value="root"/> <property name="password" value="root"/> </dataSource> </environment> </environments> <mappers> <mapper resource="UserMapper.xml"/> </mappers> </configuration>
3、准备 UserMapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="user"> <insert id="insertUser"> insert into t_user values(#{id},#{name},#{age}) </insert> <select id="selectById" resultType="org.ibatis.pojo.User"> select * from t_user where id = #{id} </select> </mapper>
4、记得在mysql中创建表t_user
/* Navicat Premium Data Transfer Source Server : localhost Source Server Type : MySQL Source Server Version : 80030 Source Host : localhost:3306 Source Schema : powernode Target Server Type : MySQL Target Server Version : 80030 File Encoding : 65001 Date: 06/10/2022 21:50:02 */ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- ---------------------------- -- Table structure for t_user -- ---------------------------- DROP TABLE IF EXISTS `t_user`; CREATE TABLE `t_user` ( `id` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL, `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL, `age` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci ROW_FORMAT = Dynamic; SET FOREIGN_KEY_CHECKS = 1;
5、写一个junit测试类
import org.ibatis.core.SqlSession; import org.ibatis.core.SqlSessionFactory; import org.ibatis.core.SqlSessionFactoryBuilder; import org.ibatis.pojo.User; import org.ibatis.utils.Resources; import org.junit.Test; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; public class TestMabtis { @Test public void testInsert() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream ins = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(ins); SqlSession sqlSession = factory.OpenSession(); User user = new User("222","jack","12"); int count = sqlSession.insert("user.insertUser", user); System.out.println(count); sqlSession.commit(); sqlSession.close(); } @Test public void testSelecOne() { SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder(); InputStream ins = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = sqlSessionFactoryBuilder.build(ins); SqlSession sqlSession = factory.OpenSession(); Object object = sqlSession.selectOne("user.selectById", "222"); System.out.println(object.toString()); sqlSession.commit(); sqlSession.close(); } }
四、总结
手写mybatis框架,主要就是与那几个重要对象相关:
SqlSessionFactoryBuilder >>> SqlSessionFactory >>> SqlSession 1、SqlSessionFactoryBuilder 对象 负责 解析 核心配置文件mybatis-config.xml, 获取到数据源信息,事务管理器信息,sql标签信息,最终返回SqlSessionFactory 对象 2、通过SqlSessionFactory对象获取SqlSession对象,开启会话 3、一个SqlSessionFactory 对象可以开启多个SqlSession会话 4、一个数据库,对应一个SqlSessionFactory对象
五、源码地址
https://download.csdn.net/download/weixin_43860634/87360297