仿照 Mybatis 手写 ROM 框架
一、问题描述
1. 代码片段

2. 问题清单:
如上图所示,传统的 JDBC 连接存在下列问题:
- 创建数据库连接存在硬编码问题
- 频繁的数据库连接与释放
- 组织查询
SQL存在硬编码问题 SQL参数个数的变化会导致代码的修改,不易维护- 封装结果集存在硬编码问题,
POJO属性的修改会带来结果集封装代码的变更,不易维护
3. 解决思路
- 将数据库配置信息及
SQL语句转移至xml中(解决硬编码问题) - 使用数据库连接池来获取数据库连接(解决频繁操作数据库连接问题)
- 通过反射、自省功能对结果集进行封装
二、代码
既然问题已经罗列出来了,那么接下来就通过自己手写的 ROM 框架将传统 JDBC 中存在的问题来解决一下。
1. 编写自定义数据库连接配置文件及 SQL 查询文件
1.1 数据库配置文件: sqlMapConfig.xml
<configuration><dataSource><property name="driverClass" value="com.mysql.jdbc.Driver">property><property name="jdbcUrl" value="jdbc:mysql://localhost:3306/stage_1_mybatis?characterEncoding=utf-8">property><property name="user" value="root">property><property name="password" value="root">property>dataSource><mapper resource="UserMapper.xml">mapper>
configuration>
1.2 SQL 配置文件: UserMapper.xml
<mapper nameSpace="com.idol.dao.IUserDao"><select id="findAll" resultType="pojo.User">select * from userselect><select id="findByCondition" paramterType="pojo.User" resultType="pojo.User">select * from user where id=#{id} and name=#{name}select>mapper>
2. 配置文件解析
2.1 xml 资源加载工具类: Resource
上面两个步骤已经将, JDBC 中硬编码部分提取成了 xml 文件,因此为了方便对 xml 配置文件进行加载,需要编写资源加载类。
import java.io.InputStream;/*** @author Supreme_Sir* @version 1.0* @className Resource* @description* @date 2020/9/24 22:39**/
public class Resource {/*** xml 资源加载器*/public static InputStream getResourceAsStream(String path) {InputStream resourceAsStream = Resource.class.getClassLoader().getResourceAsStream(path);return resourceAsStream;}
}
2.2 数据库配置文件: sqlMapConfig.xml 解析
import com.idol.io.Resource;
import com.idol.pojo.Configeration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;/*** 数据库配置文件解析类** @author Supreme_Sir* @version 1.0* @className XMLConfigBuilder* @description* @date 2020/9/26 15:22**/
public class XMLConfigBuilder {private Configeration configeration;public XMLConfigBuilder() {this.configeration = new Configeration();}/*** 该方法用于将 xml 解析成配置文件对象* @param inputStream sqlMapConfig.xml 配置文件的 InputStream 对象*/public Configeration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {// 解析 sqlMapConfig.xmlDocument document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();List<Element> propertyList = rootElement.selectNodes("//property");Properties dataSourceProperties = new Properties();for (Element element : propertyList) {String name = element.attributeValue("name");String value = element.attributeValue("value");dataSourceProperties.put(name, value);}// 创建 c3p0 数据库连接池ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();comboPooledDataSource.setDriverClass(dataSourceProperties.getProperty("driverClass"));comboPooledDataSource.setJdbcUrl(dataSourceProperties.getProperty("jdbcUrl"));comboPooledDataSource.setUser(dataSourceProperties.getProperty("user"));comboPooledDataSource.setPassword(dataSourceProperties.getProperty("password"));this.configeration.setDataSource(comboPooledDataSource);// 解析 Mapper.xmlList<Element> mapperList = rootElement.selectNodes("//mapper");for (Element element : mapperList) {String mapperPath = element.attributeValue("resource");InputStream mapperStream = Resource.getResourceAsStream(mapperPath);XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(this.configeration);xmlMapperBuilder.parse(mapperStream);}return this.configeration;}
}
2.3 SQL 配置文件:UserMapper.xml 解析
import com.idol.pojo.Configeration;
import com.idol.pojo.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;import java.io.InputStream;
import java.util.List;/*** SQL 配置文件解析类** @author Supreme_Sir* @version 1.0* @className XMLMapperBuilder* @description* @date 2020/9/26 15:45**/
public class XMLMapperBuilder {private Configeration configeration;public XMLMapperBuilder(Configeration configeration) {this.configeration = configeration;}public void parse(InputStream inputStream) throws DocumentException {Document document = new SAXReader().read(inputStream);Element rootElement = document.getRootElement();String nameSpace = rootElement.attributeValue("nameSpace");// 获取根节点下所有一级子节点List<Element> sqlElement = rootElement.elements();for (Element element : sqlElement) {String id = element.attributeValue("id");String resultType = element.attributeValue("resultType");String paramterType = element.attributeValue("paramterType");String sqlText = element.getTextTrim();MappedStatement mappedStatement = new MappedStatement(id, paramterType, resultType, sqlText);String mappedStatementName = nameSpace + "." + id;this.configeration.putMappedStatement(mappedStatementName, mappedStatement);}}
}
3. 创建 SQL 执行函数
3.1 创建执行 SQL 的统一方法
import com.idol.config.BondSql;
import com.idol.pojo.Configeration;
import com.idol.pojo.MappedStatement;
import com.idol.utils.GenericTokenParser;
import com.idol.utils.ParameterMapping;
import com.idol.utils.ParameterMappingTokenHandler;import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;/*** 创建数据库连接并执行 SQL 语句** @author Supreme_Sir* @version 1.0* @className SimpleExecutor* @description* @date 2020/9/26 16:48**/
public class SimpleExecutor implements Executor {/*** 执行 SQL** @param configeration 解析出来的数据库配置文件 DO 对象* @param mappedStatement 解析出来的 SQL 配置文件 DO 对象* @param params 条件查询参数* @param * @return* @throws Exception*/ @Overridepublic <E> List<E> query(Configeration configeration, MappedStatement mappedStatement, Object... params) throws Exception {// 创建数据库连接Connection connection = configeration.getDataSource().getConnection();String sql = mappedStatement.getSql();// 将 SQL 配置文件中的 SQL 解析为可供 PreparedStatement 使用的 SQL 语句BondSql bondSql = getBondSql(sql);PreparedStatement preparedStatement = connection.prepareStatement(bondSql.getSqlText());// 将条件查询参数配置到 SQL 语句中String parameterType = mappedStatement.getParameterType();Class<?> parameterTypeClass = getClassType(parameterType);List<ParameterMapping> parameterMappingList = bondSql.getParameterMappingList();for (int i = 0; i < parameterMappingList.size(); i++) {ParameterMapping parameterMapping = parameterMappingList.get(i);String name = parameterMapping.getContent();Field field = parameterTypeClass.getDeclaredField(name);// 暴力访问对象属性field.setAccessible(true);// 获取参数中指定属性的值Object o = field.get(params[0]);preparedStatement.setObject(i + 1, o);}// 执行 SQL 查询语句ResultSet resultSet = preparedStatement.executeQuery();// 开始封装查询结果String resultType = mappedStatement.getResultType();Class<?> resultTypeClass = getClassType(resultType);List result = new ArrayList();while (resultSet.next()) {// 实例化查询结果对象Object instance = resultTypeClass.newInstance();ResultSetMetaData metaData = resultSet.getMetaData();for (int i = 1; i <= metaData.getColumnCount() ; i++) {String columnName = metaData.getColumnName(i);Object val = resultSet.getObject(columnName);// 为查询结果实例赋值PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);Method writeMethod = propertyDescriptor.getWriteMethod();writeMethod.invoke(instance, val);}result.add(instance);}return result;}/*** 根据 SQL 配置文件中的类路径获取该对象的 Class** @param classPath* @return* @throws ClassNotFoundException*/private Class<?> getClassType(String classPath) throws ClassNotFoundException {if (classPath != null) {return Class.forName(classPath);}return null;}/*** 将包含 #{} 的 SQL 解析为可供 PreparedStatement 使用的 SQL** @param sql* @return*/private BondSql getBondSql(String sql) {// 参数标识处理类ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();// 解析 xml 文件中的 SQLGenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);String parsedSql = genericTokenParser.parse(sql);// 获取 SQL 中的参数名称List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();return new BondSql(parsedSql, parameterMappings);}
}
3.2 创建具体的 SQL 查询方法
import com.idol.pojo.Configeration;import java.lang.reflect.*;
import java.util.List;/*** 封装具体的 SQL 查询方法** @author Supreme_Sir* @version 1.0* @className DefaultSqlSession* @description* @date 2020/9/26 16:34**/
public class DefaultSqlSession implements SqlSession {private Configeration configeration;@Overridepublic <E> List<E> selectAll(String statementID, Object... params) throws Exception {SimpleExecutor simpleExecutor = new SimpleExecutor();return simpleExecutor.query(this.configeration, this.configeration.getMappedStatement(statementID), params);}@Overridepublic <T> T selectOne(String statementID, Object... params) throws Exception {List<T> objectList = selectAll(statementID, params);if (objectList.size() == 1) {return objectList.get(0);} else {throw new RuntimeException("查询结果为空或查询结果不唯一!!!");}}/*** 通过 Java 动态代理执行 SQL** @param mapperClass* @param * @return*/ @Overridepublic <T> T getMapper(Class<?> mapperClass) {Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 由于代理对象无法获取 SQL 配置文件中的相关信息,因此通过类名和方法名的拼接,完成成对应 id SQL 的调用String methodName = method.getName();String className = method.getDeclaringClass().getName();String statementID = className + "." + methodName;// 判断返回结果是否存在泛型,如果存在泛型则表示改结果为集合,则调用 selectAll 方法Type returnType = method.getGenericReturnType();if (returnType instanceof ParameterizedType) {return selectAll(statementID, args);}return selectOne(statementID, args);}});return (T) proxyInstance;}public DefaultSqlSession(Configeration configeration) {this.configeration = configeration;}
}
4. 方法测试
4.1 创建数据表: user.sql
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (`id` int(10) NOT NULL,`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '张三');
INSERT INTO `user` VALUES (2, '李四');
INSERT INTO `user` VALUES (3, '王五');
INSERT INTO `user` VALUES (4, '赵六');
INSERT INTO `user` VALUES (5, '冯七');SET FOREIGN_KEY_CHECKS = 1;
4.2 编写测试方法
import com.idol.dao.IUserDao;
import com.idol.io.Resource;
import com.idol.sql.session.SqlSession;
import com.idol.sql.session.SqlSessionFactory;
import com.idol.sql.session.SqlSessionFactoryBuilder;
import org.junit.Test;
import pojo.User;import java.io.InputStream;
import java.util.List;/*** @author Supreme_Sir* @version 1.0* @className MyFrameworkTest* @description* @date 2020/9/24 22:44**/
public class MyFrameworkTest {@Testpublic void getResourceAsStramTest() throws Exception {InputStream resourceAsStream = Resource.getResourceAsStream("sqlMapConfig.xml");SqlSessionFactory sqlsessionFactory = new SqlSessionFactoryBuilder().builder(resourceAsStream);SqlSession sqlSession = sqlsessionFactory.openSession();IUserDao userDao = sqlSession.getMapper(IUserDao.class);List<User> userList = userDao.findAll();System.out.println("SelectAll~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");for (User user : userList) {System.out.println(user);}System.out.println("\r\nSelectOne~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");User user = userDao.findByCondition(new User(1, "张三"));System.out.println(user);}
}
4.3 执行结果
SelectAll~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
User{id=1, name='张三'}
User{id=2, name='李四'}
User{id=3, name='王五'}
User{id=4, name='赵六'}
User{id=5, name='冯七'}SelectOne~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
User{id=1, name='张三'}
4.4 源码
下载源码
-------------------- 你在朋友圈看到的华丽照片,P掉了多少生活中的苦难 --------------------
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
