南阳网站托管,北京网站设计推荐柚米,wordpress搜索不了中文linux,电子商务文章目录 第二十章 简单ORM框架实现背景技术背景基本概念工作原理优点缺点常见的ORM框架 业务背景 目标设计实现代码结构类图实现步骤 测试事先准备属性配置文件测试用例(selectOne)测试结果测试用例(selectList)测试结果 总结 第二十章 简单ORM框架实现
背景
技术背景
ORMObject-Relational Mapping是一种用于简化开发的技术它通过将面向对象编程语言中的对象与关系型数据库中的表进行映射使得开发者可以用面向对象的方式操作数据库而无需直接编写SQL语句。以下是对ORM的详细解释
基本概念
ORM全称为对象关系映射Object-Relational Mapping它是一种编程技术用于在面向对象的编程语言和关系型数据库管理系统之间建立桥梁。ORM通过将编程语言中的类映射为数据库中的表将类的实例对象映射为表中的记录实现了对象和关系型数据库之间的数据交互。
工作原理
ORM技术允许开发者以面向对象的方式操作数据例如创建、读取、更新和删除对象而不需要直接写SQL语句。在ORM中数据库表被视为对象类表中的每一条记录被视为该类的一个实例表的列则映射为对象的属性。ORM框架通常提供了一系列的API和工具使得开发人员可以通过面向对象的方式来进行数据库操作如查询、插入、更新和删除等。
优点
ORM技术极大地简化了应用程序中的数据访问层开发提高了开发效率和代码的可维护性。ORM通过面向对象的方式来操作数据库使得代码更加易于维护和扩展同时也提高了代码的可读性和可重用性。ORM支持多种数据库使得开发人员可以更加灵活地选择数据库提高了代码的可移植性和可扩展性。
缺点
ORM框架的性能可能不如手写SQL对于大量数据的查询和操作可能会有一定的性能损失。ORM框架需要掌握一定的知识和技能学习成本较高。不同的ORM框架对于相同的数据类型和操作可能会有不同的支持程度可能存在可移植性的问题。ORM框架通常需要进行配置和映射复杂性较高需要一定的技术水平和经验。
常见的ORM框架
JavaHibernate、MyBatis等。PythonSQLAlchemy、Django ORM、Peewee等。.NETEntity Framework (EF) Core等。PHPLaravel ORM、Yii ORM、ThinkPHP ORM等。
业务背景
ORM 对象关系映射是一种程序设计技术用于实现面向对象编程语言里不同类型系统的数据之间的转换也让我们可以更方便的使用数据库。那么怎样实现类似于 MyBatis 这样的 ORM 框架呢本章节我们就来以实现一个 ORM 框架为目标看看该怎么设计和实现。另外关于 ORM 框架的实现下一章节我们会继续完善这个ORM框架并与前面章节实现的small-spring框架做对接。
目标
实现一个简易版的类似mybatis的orm框架屏蔽了对 JDBC 操作的复杂性, 让外部的调用方可以更加简单的方式使用数据库。
设计
一个ORM框架要实现的核心内容 包括参数映射、SQL解析、SQL执行、结果映射,这块的内容会被封装对内通过jdbc与数据库进行交互对外提供SqlSession工厂类进行接口调用。
整体设计结构如下图 实现
代码结构 源码实现https://github.com/swg209/spring-study/tree/main/step20-simple-orm
类图 在整个类图中SqlSession是与数据库交互的核心接口定义了多种查询方法包括selectOne和selectList分别用于查询单个对象和对象列表同时提供了close方法用于关闭会话。依赖于SqlSessionFactory接口来创建其实例。SqlSessionFactory负责创建SqlSession实例通过其openSession方法实现。DefaultSqlSession类是SqlSession接口的具体实现类提供了selectOne、selectList和close等方法的实现。包含了数据库连接connection和映射元素mapperElement等属性用于管理数据库连接和SQL映射。提供了resultSet2Obj和buildParameter等辅助方法用于结果集到对象的转换和参数构建。DefaultSqlSessionFactory类是SqlSessionFactory接口的具体实现类负责创建和管理SqlSession对象。包含了配置configuration属性用于存储数据库连接和SQL映射的配置信息。通过其openSession方法返回SqlSession实例。SqlSessionFactoryBuilder类用于构建SqlSessionFactory实例通过其build方法读取配置文件并创建DefaultSqlSessionFactory对象。在构建过程中会解析配置文件并生成相应的配置信息。
实现步骤 定义sqlSession接口 public interface SqlSession {T T selectOne(String statement, Object parameter);T T selectOne(String statement);T ListT selectList(String statement, Object parameter);T ListT selectList(String statement);void close();}定义sqlSession具体实现类 DefaultSqlSession public class DefaultSqlSession implements SqlSession {//连接信息.private Connection connection;//配置信息.private MapString, XNode mapperElement;public DefaultSqlSession(Connection connection, MapString, XNode mapperElement) {this.connection connection;this.mapperElement mapperElement;}Overridepublic T T selectOne(String statement) {try {XNode xNode mapperElement.get(statement);PreparedStatement preparedStatement connection.prepareStatement(xNode.getSql());ResultSet resultSet preparedStatement.executeQuery();ListT objects resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}private T ListT resultSet2Obj(ResultSet resultSet, Class? clazz) {ListT list new ArrayList();try {ResultSetMetaData metaData resultSet.getMetaData();int columnCount metaData.getColumnCount();//每次遍历一行值while (resultSet.next()) {//创建对象T obj (T) clazz.newInstance();//遍历每一列for (int i 1; i columnCount; i) {//获取列名String columnName metaData.getColumnName(i);//驼峰命名columnName WordUtils.capitalizeFully(columnName, new char[]{_}).replace(_, );//获取列值Object value resultSet.getObject(i);//获取set方法String setMethod set columnName.substring(0, 1).toUpperCase() columnName.substring(1);Method method;if (value instanceof Timestamp) {method clazz.getMethod(setMethod, Date.class);//调用set方法} else {method clazz.getMethod(setMethod, value.getClass());//调用set方法}method.invoke(obj, value);}list.add(obj);}} catch (Exception e) {e.printStackTrace();}return list;}Overridepublic T T selectOne(String statement, Object parameter) {XNode xNode mapperElement.get(statement);MapInteger, String parameterMap xNode.getParameter();try {PreparedStatement preparedStatement connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, parameterMap);ResultSet resultSet preparedStatement.executeQuery();ListT objects resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects.get(0);} catch (Exception e) {e.printStackTrace();}return null;}private void buildParameter(PreparedStatement preparedStatement,Object parameter,MapInteger, String parameterMap) throws SQLException, IllegalAccessException {int size parameterMap.size();// 单个参数if (parameter instanceof Long) {for (int i 1; i size; i) {preparedStatement.setLong(i, Long.parseLong(parameter.toString()));}return;}if (parameter instanceof Integer) {for (int i 1; i size; i) {preparedStatement.setInt(i, Integer.parseInt(parameter.toString()));}return;}if (parameter instanceof String) {for (int i 1; i size; i) {preparedStatement.setString(i, parameter.toString());}return;}MapString, Object fieldMap new HashMap();// 对象参数Field[] declaredFields parameter.getClass().getDeclaredFields();for (Field field : declaredFields) {String name field.getName();field.setAccessible(true);Object obj field.get(parameter);field.setAccessible(false);fieldMap.put(name, obj);}for (int i 1; i size; i) {String parameterDefine parameterMap.get(i);Object obj fieldMap.get(parameterDefine);if (obj instanceof Short) {preparedStatement.setShort(i, Short.parseShort(obj.toString()));continue;}if (obj instanceof Integer) {preparedStatement.setInt(i, Integer.parseInt(obj.toString()));continue;}if (obj instanceof Long) {preparedStatement.setLong(i, Long.parseLong(obj.toString()));continue;}if (obj instanceof String) {preparedStatement.setString(i, obj.toString());continue;}if (obj instanceof Date) {preparedStatement.setDate(i, (java.sql.Date) obj);}}}Overridepublic T ListT selectList(String statement, Object parameter) {try {XNode xNode mapperElement.get(statement);PreparedStatement preparedStatement connection.prepareStatement(xNode.getSql());buildParameter(preparedStatement, parameter, xNode.getParameter());ResultSet resultSet preparedStatement.executeQuery();ListT objects resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects;} catch (Exception e) {e.printStackTrace();}return null;}Overridepublic T ListT selectList(String statement) {try {XNode xNode mapperElement.get(statement);PreparedStatement preparedStatement connection.prepareStatement(xNode.getSql());ResultSet resultSet preparedStatement.executeQuery();ListT objects resultSet2Obj(resultSet, Class.forName(xNode.getResultType()));return objects;} catch (Exception e) {e.printStackTrace();}return null;}Overridepublic void close() {}
}定义sqlSessionFactory接口 public interface SqlSessionFactory {SqlSession openSession();
} 定义sqlSessionFactory具体实现类 DefaultSqlSessionFactory public class DefaultSqlSessionFactory implements SqlSessionFactory {private final Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration configuration;}Overridepublic SqlSession openSession() {// 创建会话return new DefaultSqlSession(configuration.connection, configuration.mapperElement);}
} 定义SqlSessionFactoryBuilder实现读取配置中的xml文件解析获取配置源信息dataSource、
SQL语句信息mapperElement、数据库连接信息Connection 写入Configuration
public class SqlSessionFactoryBuilder {/*** 构建会话工厂.** param reader* return*/public SqlSessionFactory build(Reader reader) {SAXReader saxReader new SAXReader();try {Document document saxReader.read(new InputSource(reader));Configuration configuration parseConfiguration(document.getRootElement());return new DefaultSqlSessionFactory(configuration);} catch (DocumentException e) {e.printStackTrace();}return null;}private Configuration parseConfiguration(Element root) {Configuration configuration new Configuration();configuration.setDataSource(dataSource(root.selectNodes(//dataSource)));configuration.setConnection(connection(configuration.dataSource));configuration.setMapperElement(mapperElement(root.selectNodes(mappers)));return configuration;}/*** 获取数据源配置信息.*/private MapString, String dataSource(ListElement list) {MapString, String dataSource new HashMap(4);Element element list.get(0);List content element.content();for (Object o : content) {Element e (Element) o;String name e.attributeValue(name);String value e.attributeValue(value);dataSource.put(name, value);}return dataSource;}/*** 获取connection信息.*/private Connection connection(MapString, String dataSource) {try {Class.forName(dataSource.get(driver));return DriverManager.getConnection(dataSource.get(url), dataSource.get(username), dataSource.get(password));} catch (ClassNotFoundException | SQLException e) {e.printStackTrace();}return null;}/*** 获取SQL语句信息.*/private MapString, XNode mapperElement(ListElement list) {MapString, XNode map new HashMap();Element element list.get(0);List content element.content();for (Object o : content) {Element e (Element) o;String resource e.attributeValue(resource);try {Reader reader Resources.getResourceAsReader(resource);SAXReader saxReader new SAXReader();Document document saxReader.read(new InputSource(reader));Element root document.getRootElement();//命名空间String namespace root.attributeValue(namespace);//SELECTListElement selectNodes root.selectNodes(select);for (Element node : selectNodes) {String id node.attributeValue(id);String parameterType node.attributeValue(parameterType);String resultType node.attributeValue(resultType);String sql node.getText();// ? 匹配MapInteger, String parameter new HashMap();Pattern pattern Pattern.compile((#\\{(.*?)}));Matcher matcher pattern.matcher(sql);for (int i 1; matcher.find(); i) {String g1 matcher.group(1);String g2 matcher.group(2);parameter.put(i, g2);sql sql.replace(g1, ?);}XNode xNode new XNode();xNode.setNamespace(namespace);xNode.setId(id);xNode.setParameterType(parameterType);xNode.setResultType(resultType);xNode.setSql(sql);xNode.setParameter(parameter);map.put(namespace . id, xNode);}} catch (Exception ex) {ex.printStackTrace();}}return map;}
}
测试
事先准备
mysql数据库配置好连接信息, 建表语句。
#创建数据库()
CREATE DATABASE IF NOT EXISTS mybatis;#创建用户表
USE mybatis;CREATE TABLE my_user (id bigint NOT NULL AUTO_INCREMENT COMMENT 自增ID,user_id varchar(9) DEFAULT NULL COMMENT 用户ID,user_head varchar(16) DEFAULT NULL COMMENT 用户头像,create_time timestamp NULL DEFAULT NULL COMMENT 创建时间,update_time timestamp NULL DEFAULT NULL COMMENT 更新时间,user_name varchar(64) DEFAULT NULL COMMENT 用户名,user_password varchar(64) DEFAULT NULL COMMENT 用户密码,PRIMARY KEY (id)
) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;# 造数
INSERT INTO my_user (user_id, user_head, create_time, update_time, user_name, user_password) VALUES
(1, 头像1, 2024-11-12 18:00:12, 2024-11-12 18:00:12, 小苏1, s123asd),
(2, 头像2, 2024-11-12 18:00:12, 2024-11-12 18:00:12, 小苏2, s123asd);
User类
创建 User类承载数据库表的对象数据。IUserDao定义用户表Dao查询方法。
public class User {private Long id;private String userId; // 用户IDprivate String userName; // 昵称private String userHead; // 头像private String userPassword; // 密码private Date createTime; // 创建时间private Date updateTime; // 更新时间public User() {}public User(String userName) {this.userName userName;}public Long getId() {return id;}public void setId(Long id) {this.id id;}public String getUserId() {return userId;}public void setUserId(String userId) {this.userId userId;}public String getUserName() {return userName;}public void setUserName(String userNickName) {this.userName userNickName;}public String getUserHead() {return userHead;}public void setUserHead(String userHead) {this.userHead userHead;}public String getUserPassword() {return userPassword;}public void setUserPassword(String userPassword) {this.userPassword userPassword;}public Date getCreateTime() {return createTime;}public void setCreateTime(Date createTime) {this.createTime createTime;}public Date getUpdateTime() {return updateTime;}public void setUpdateTime(Date updateTime) {this.updateTime updateTime;}}
IUserDao
public interface IUserDao {User queryUserInfoById(Long id);ListUser queryUserList(User user);
}
属性配置文件
mybatis-config-datasource.xml** ORM配置文件
配置数据库源连接信息配置mappers声明.
?xml version1.0 encodingUTF-8?
!DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//ENhttp://mybatis.org/dtd/mybatis-3-config.dtdconfigurationenvironments defaultdevelopmentenvironment iddevelopmenttransactionManager typeJDBC/dataSource typePOOLEDproperty namedriver valuecom.mysql.cj.jdbc.Driver/property nameurl valuejdbc:mysql://127.0.0.1:3306/mybatis?useUnicodetrue/property nameusername valueroot/property namepassword value123456//dataSource/environment/environmentsmappersmapper resourcemapper/User_Mapper.xml//mappers/configuration
User_Mapper.xml mapper配置文件
配置用户表查询方法.
?xml version1.0 encodingUTF-8?
!DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd
mapper namespacecn.suwg.springframework.mybatis.test.dao.IUserDaoselect idqueryUserInfoById parameterTypejava.lang.LongresultTypecn.suwg.springframework.mybatis.test.po.UserSELECT id, user_id, user_name, user_head, user_password, create_timeFROM my_userwhere id #{id}/selectselect idqueryUserList parameterTypecn.suwg.springframework.mybatis.test.po.UserresultTypecn.suwg.springframework.mybatis.test.po.UserSELECT id, user_id, user_name, user_head, user_password, create_time, update_timeFROM my_userwhere user_name #{userName}/select/mapper测试用例(selectOne)
public class ApiTest {Testpublic void test_queryUserInfoById() {String resource mybatis-config-datasource.xml;Reader reader;try {reader Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper new SqlSessionFactoryBuilder().build(reader);SqlSession session sqlMapper.openSession();try {User user session.selectOne(cn.suwg.springframework.mybatis.test.dao.IUserDao.queryUserInfoById, 1L);System.out.println(JSON.toJSONString(user));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}
}测试结果 测试用例(selectList)
public class ApiTest {Testpublic void test_queryUserInfoList() {String resource mybatis-config-datasource.xml;Reader reader;try {reader Resources.getResourceAsReader(resource);SqlSessionFactory sqlMapper new SqlSessionFactoryBuilder().build(reader);SqlSession session sqlMapper.openSession();try {ListUser userList session.selectList(cn.suwg.springframework.mybatis.test.dao.IUserDao.queryUserList,new User(小苏));System.out.println(JSON.toJSONString(userList));} finally {session.close();reader.close();}} catch (Exception e) {e.printStackTrace();}}
}测试结果 从测试结果中可以看到可以正常与数据库进行交互可以正常查询到用户表的数据。
总结 在本章节中我们简单实现一个类似mybatis的ORM框架当前只是实现了基本功能后续可以基于这个框架再进行完善。 通过编写测试用例验证了该 ORM 框架的基本功能包括查询单个对象和对象列表。测试结果表明该框架能够正常与数据库进行交互并正确映射查询结果到 Java 对象。 参考书籍《手写Spring渐进式源码实践》 书籍源代码https://github.com/fuzhengwei/small-spring