文章目录

  • 一 对象记录映射
  • 二 效率和性能
  • 三 数据访问描述信息
  • 四 字段类型转换
  • 五 数据访问基类
  • 六 统一异常处理
  • 七 数据访问框架
  • 八 数据源创建工厂
  • 九 SQL指令解析
  • 十 补充DAO接口实现
  • 十一 测试
  • 十二 其他问题

一 对象记录映射

  本质上来说,ORM框架需要处理的就是如何将JAVA对象与数据表记录进行关联,便于JAVA对象的持久化,以及将表记录自动转换为JAVA对象。

  这就需要存在一个中间态的描述信息,描述信息必须能够指明JAVA对象和表记录的映射关系,包括但不限于JAVA对象类型和表的关系,对象成员和字段的关系。

  市面上成熟的ORM框架很多,但总体设计思路一定离不开中间描述信息,典型的如Mybaties,它通过配置文件来描述,又如Spring通过配置或者注解来描述。

  我们称这种与平台、语言无关的描述信息为IDL,依赖于这种设计,实际上对配置的要求变高了,而且当数据表量激增时很难维护。

二 效率和性能

  无论是通过文件配置还是通过注解的方式来描述对象记录的映射关系,都没办法直接将SQL字段类型直接转换为JAVA对象的成员类型,而且为了能够准确的定位到对象成员,必须使用大量的反射才能实现。

  只要涉及反射,那么效率和性能就很难得到保障。那么有没有一种办法能够避开这种低效率高消耗的映射实现呢?是可以做到的,我们可以赋予DAO对象一种具备自我描述的能力,无需再通过读取配置文件或者通过注解反射来实现对象和记录的映射关系。

  这也是本文设计的出发点,尽可能的通过对象自身的信息来完成映射转换,避免使用反射等低效高消耗的操作。

三 数据访问描述信息

  要赋予DAO对象自我描述的能力,需要设计一组可供采集信息的接口定义:

public interface DataAccessDescription {public String getTableName();public String[] getPrimaryKeyArray();public String[] getIndexArray();public Map<String, SqlData> getFieldMap();
}

  本文仅提供设计思路,并不完全实现所有功能,上述接口定义已经可以满足示意的需求。一个DAO对象可提供自身对应的表名,主键集合、索引集合以及表字段和DAO对象成员的映射关系,这些信息足以应对简单的CRUD操作。

  注意getFieldMap方法返回的Map对象中,其Value类型是SqlData,关于SqlData的说明见下文。

四 字段类型转换

  映射关系解决之后,还有一项非常重要的事情,如何将SQL字段值赋予对象成员。

  首先我们无法保证SQL返回的类型一定和成员类型匹配,比如说表字段为varchar类型,那么SQL返回的应该是String类型,而成员类型很可能是int。

  第二点,ORM在将记录转化为JAVA对象的时候,必然要对成员赋值,这就涉及到如何访问对象成员的问题,我相信大家的第一反应一定是反射,然而不通过反射我们一样可以做到。

  我们需要赋予DAO对象成员能够对SQL返回值自动转化并赋予成员的能力,定义接口SqlData来表示所有的DAO成员:

public interface SqlData {Object getData();void setSqlData(Object value);
}

  接口定义完毕后,需要对其进行实现,以满足DAO成员类型的需求,这里以String作为样例供大家参考:

public class SqlString implements SqlData {private String value = null;public SqlString() {this.value = null;}public SqlString(String value) {this.value = value;}public String getValue() {return value;}public void setValue(String value) {this.value = value;}public Object getData() {return this.value;}public void setSqlData(Object value) {if (value instanceof String) {setValue((String) value);} else {setValue(String.valueOf(value));}}
}

五 数据访问基类

  目前我们已经赋予了DAO自我描述,以及成员的自动转换赋值的能力,为了便于应用的使用,我们还需要在DAO基类中加入常规的CRUD操作,如此的话其定义如下:

public abstract class Dao implements DataAccessDescription {public boolean select() throws DataAccessException {return false;}public boolean update() throws DataAccessException {return false;}public boolean insert() throws DataAccessException {return false;}public boolean delete() throws DataAccessException  {return false;}
}

  注意所有的CRUD方法都可能抛出DataAccessException异常,它的介绍见下文。

六 统一异常处理

  为了便于异常处理,我们需要定义一个受检查的异常类型,用以描述所有归属与ORM框架的异常:

public class DataAccessException extends Exception {private static final long serialVersionUID = 1L;public DataAccessException() {super();}public DataAccessException(String message) {super(message);}public DataAccessException(Throwable t) {super(t);}public DataAccessException(String message, Throwable t) {super(message, t);}}

七 数据访问框架

  DAO相关设计基本上结束了,接下来需要处理和数据库交互的问题了。我们把所有和数据库交互的功能都封装在数据访问框架中,它仅关注向数据库提交的SQL指令:

public class DataAccessFramework implements ConnectionManager {private static final String DATA_SOURCE_TYPE = "type";private static class DataAccessFrameworkHolder {static DataAccessFramework daf = new DataAccessFramework();}public static DataAccessFramework getInstance() {return DataAccessFrameworkHolder.daf;}private DataAccessFramework() {}private DataSource dataSource = null;public Connection getConnection() throws DataAccessException {try {if (dataSource == null) {Properties properties = PropertiesUtils.loadProperties("ds.properties");dataSource = DataSourceFactory.getDataSource(properties.getProperty(DATA_SOURCE_TYPE), properties);}return dataSource.getConnection();} catch (Exception e) {throw new DataAccessException(e);}}public boolean execute(String command) throws DataAccessException {return execute(command, null);}/*** 用于提交非查询类SQL指令** @param command SQL指令* @param params  预编译指令参数* @return 执行结果* @throws DataAccessException*/public boolean execute(String command, Object[] params) throws DataAccessException {return execute(command, params, null) > 0 ? true : false;}/*** 用于提交查询类SQL指令** @param command SQL指令* @param params  预编译指令参数* @param rets    查询结果集* @return 当SQL指令为更新删除等操作时,返回值为数据库受影响的行数;当SLQ指令为查询语句时返回值为结果条目数* @throws DataAccessException*/public int execute(String command, Object[] params, Object[] rets) throws DataAccessException {Connection conn = getConnection();if (conn == null) {throw new DataAccessException("无法获取数据库连接");}PreparedStatement stmt = null;ResultSet rs = null;try {stmt = conn.prepareStatement(command);if (params.length > 0) {for (int i = 0; i < params.length; i++) {stmt.setObject(i + 1, params[i]);}}System.out.println(stmt);if (rets != null) {rs = stmt.executeQuery();int count = 0;while (rs.next()) {for (int i = 0; i < rets.length; i++) {rets[i] = rs.getObject(i + 1);}++count;}return count;} else {return stmt.executeUpdate();}} catch (SQLException e) {throw new DataAccessException(e);} finally {DataAccessUtils.close(conn, stmt, rs);}}
}

  实际上和数据库进行交互时,无非就三种场景需要考虑:

  1. 直接提交SQL指令
  2. 提交带有预编译参数的SQL指令
  3. 提交带有预编译参数的SQL查询指令

  所以数据访问框架的核心接口就三个execute方法。

  注意数据访问框架不仅需要解决与数据库交互的问题,还需要维护数据库连接,那么如果采用连接池组件,各类数据源的维护就变得复杂起来,所以在数据访问框架看来,它只关注DataSource,而DataSource实例的创建则被封装在DataSourceFactory内部,见下文。

八 数据源创建工厂

  为了支持不同的数据库连接池组件,以及各类数据源组件,我们将DataSource对象的创建过程封装起来,通过配置来应对不同的实现,这里以常见的连接池组件为例,示意实现过程如下:

public final class DataSourceFactory {private static final String DEFAULT_DATA_SOURCE = "default";private static final String C3P0_DATA_SOURCE = "c3p0";private static final String DRUID_DATA_SOURCE = "druid";private static final String HIKARI_DATA_SOURCE = "hikari";private DataSourceFactory() {}public static DataSource getDataSource(String dataSourceType, Map<?, ?> properties) throws DataAccessException {try {if (dataSourceType.equals(DEFAULT_DATA_SOURCE)) {return getDefaultDataSource(properties);} else if (dataSourceType.equals(C3P0_DATA_SOURCE)) {return getC3P0DataSource(properties);} else if (dataSourceType.equals(DRUID_DATA_SOURCE)) {return getDruidDataSource(properties);} else if (dataSourceType.equals(HIKARI_DATA_SOURCE)) {return getHikareDataSource(properties);} else {throw new DataAccessException("错误的数据源类型");}} catch (Exception e) {throw new DataAccessException("无法获取数据源");}}private static DataSource getDefaultDataSource(Map<?, ?> properties) {// TODO Auto-generated method stubreturn null;}private static DataSource getC3P0DataSource(Map<?, ?> properties) {// TODO Auto-generated method stubreturn null;}private static DataSource getDruidDataSource(Map<?, ?> properties) throws Exception {return DruidDataSourceFactory.createDataSource(properties);}private static DataSource getHikareDataSource(Map<?, ?> properties) {// TODO Auto-generated method stubreturn null;}
}

九 SQL指令解析

  现在DAO的映射描述问题,以及数据库交互问题都解决了,那么怎么将DAO对象的信息转变为SQL指令呢?我们根据常见的数据访问场景来封装一个SQL指令的解析器,由它来将一个DAO对象转变为不同访问操作的SQL指令:

public class SqlCommandParser {public static String getSelectCommand(String tableName, String[] queryFields, String[] paramFields) {StringBuilder builder = new StringBuilder("SELECT ");for (int i = 0; i < queryFields.length; i++) {builder.append(queryFields[i]);if (i + 1 < queryFields.length) {builder.append(",");}}builder.append(" FROM ").append(tableName).append(" WHERE ");for (int i = 0; i < paramFields.length; i++) {builder.append(paramFields[i]);builder.append("=?");if (i + 1 < paramFields.length) {builder.append(" AND ");}}return builder.toString();}public static String getInsertCommand(String tableName, String[] fieldNames) {StringBuilder builder = new StringBuilder("INSERT INTO " + tableName + " (");StringBuilder preparedParams = new StringBuilder();for (int i = 0; i < fieldNames.length; i++) {builder.append(fieldNames[i]);preparedParams.append("?");if (i + 1 < fieldNames.length) {builder.append(",");preparedParams.append(",");} else {builder.append(") VALUES (");builder.append(preparedParams.toString());builder.append(")");}}return builder.toString();}public static void main(String[] args) {String sql = getInsertCommand("TEST", new String[]{"field1", "field2", "field3"});System.out.println(sql);}
}

十 补充DAO接口实现

  现在所有的准备工作都完毕了,但是DAO基类中还有CRUD方法没有实现,这里就利用上述基础设施来实现它,以select和insert为例:

public abstract class Dao implements DataAccessDescription {public boolean select() throws DataAccessException {String[] primaryKeyArray = getPrimaryKeyArray();if (primaryKeyArray == null || primaryKeyArray.length == 0) {throw new DataAccessException("未设置主键");}Map<String, SqlData> fieldMap = getFieldMap();if (fieldMap == null || fieldMap.size() == 0) {throw new DataAccessException("未设置字段成员映射");}String command = SqlCommandParser.getSelectCommand(getTableName(),getFieldMap().keySet().toArray(new String[getFieldMap().size()]), primaryKeyArray);Object[] params = new Object[primaryKeyArray.length];for (int i = 0; i < primaryKeyArray.length; i++) {params[i] = fieldMap.get(primaryKeyArray[i]).getData();}return DataAccessFramework.getInstance().execute(command, params, fieldMap.values().toArray()) > 0;}public boolean update() {return false;}public boolean insert() throws DataAccessException {String[] primaryKeyArray = getPrimaryKeyArray();if (primaryKeyArray == null || primaryKeyArray.length == 0) {throw new DataAccessException("未设置主键");}String[] fieldNameArray = getFieldMap().keySet().toArray(new String[getFieldMap().size()]);Object[] fieldValueArray = new Object[getFieldMap().size()];for (int i = 0; i < fieldNameArray.length; i++) {fieldValueArray[i] = getFieldMap().get(fieldNameArray[i]).getData();}String command = SqlCommandParser.getInsertCommand(getTableName(), fieldNameArray);return DataAccessFramework.getInstance().execute(command, fieldValueArray);}public boolean delete() {return false;}
}

十一 测试

  所有开发工作完毕,进入最后的测试阶段,首先创建一个名为user的表,结构如下:

  接下来创建一个与之关联的DAO:

public class User extends Dao {private SqlLong id;private SqlString uuid;private SqlString password;private SqlString nickname;private SqlString phone;private SqlDate creatime;private SqlDate updatime;public User() {this.id = new SqlLong();this.uuid = new SqlString();this.password = new SqlString();this.nickname = new SqlString();this.phone = new SqlString();this.creatime = new SqlDate();this.updatime = new SqlDate();}public long getId() {return id.getValue();}...//各种getter、setter,省略public String getTableName() {return "user";}public String[] getPrimaryKeyArray() {return new String[] { "id" };}public String[] getIndexArray() {return null;}public Map<String, SqlData> getFieldMap() {Map<String, SqlData> map = new HashMap<String, SqlData>();map.put("id", id);map.put("uuid", uuid);map.put("password", password);map.put("nickname", nickname);map.put("phone", phone);map.put("creatime", creatime);map.put("updatime", updatime);return map;}
}

  注意咯,所有的DAO成员类型都是SqlData,而且在构造时进行初始化,并且每个DAO类型都需要实现DataAccessDescription接口,以提供映射描述信息。

  再配置一下数据源相关的文件信息,在根目录下加入db.properties:

type=druid
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/***?useUnicode=true&amp;characterEncoding=utf-8&useSSL=false
username=root
password=***

  通过type指定了数据库连接池采用Druid,其他的则为数据库访问的必要信息。

  最后编写测试案例,我们创建一个user对象,将它插入到数据库中:

public class TestAccessUser {public static void main(String[] args) {User user = new User();user.setUuid(UUID.randomUUID().toString().replaceAll("-", ""));user.setNickname("test");user.setPassword("123");user.setPhone("13275186860");try {if (user.insert()) {System.out.println("插入成功");} else {System.out.println("插入失败");}} catch (DataAccessException e) {e.printStackTrace();}}
}

  编译执行结果如下:

  到数据库核对一下记录是否正确:

  在管理工具上的确看到了这条记录,还可以编写查询案例来测试一下是否能从DB查到数据并转换为User对象,这里不再详细说明了。

十二 其他问题

  一个非常简易的ORM框架就设计完毕了,这里没有用到任何表的配置文件,也没有用到任何反射技术,可以说执行效率绝对是可以保证的。

  但是这样一个简陋的ORM框架显然不能支撑一个大规模项目的需求,它还存在许多需要改进的地方:

  1. 支持批量的CRUD
  2. 完善的事务管理机制
  3. 支持部分字段的查询
  4. 支持自定义过滤条件的查询
  5. ……

  整个设计并没有花费我太多的时间,但是如何解决目前普遍存在的配置文件和注解问题困扰了我很久,其实任何设计都不需要多么复杂的技术实现,只要思路是正确的,用最简单易懂的代码实现就是完美的。

  希望这篇介绍能给朋友们一些设计思路吧。

ORM框架设计及实现相关推荐

  1. 开源XDesigner ORM 框架设计

    XDesigner ORM 框架设计 袁永福 2011-01-20 最新版本源代码下载地址 http://files.cnblogs.com/xdesigner/XDesignerORM.zip . ...

  2. orm框架设计、分析与开发

    orm框架设计.分析与开发 前面写过几篇文章介绍和分析mybatis,今天拆解下要设计一个ORM框架涉及到哪些方面,如何用现有的一些已知工具像spring jdbc.freemarker等重新造一个O ...

  3. java orm设计_大搜车orm框架设计思路

    orm基本概念 ORM,即Object-Relational Mapping(对象关系映射),它的作用是在关系型数据库和业务实体对象之间作一个映射,这样,我们在具体的操作业务对象的时候,就不需要再去和 ...

  4. 由ORM框架设计看到微软与苹果的最大差别

    一不小心加入水果党快半年了,也算得上是经历过微软和苹果两种技术的人.尽管不是那么的精通,但也是马马虎虎会用两个平台进行开发,写这篇文章完全是涂鸦之作,是一个用过两家公司技术的人的个人小看法. 微软和苹 ...

  5. Rafy 领域实体框架设计 - 重构 ORM 中的 Sql 生成

    前言 Rafy 领域实体框架作为一个使用领域驱动设计作为指导思想的开发框架,必然要处理领域实体到数据库表之间的映射,即包含了 ORM 的功能.由于在 09 年最初设计时,ORM 部分的设计并不是最重要 ...

  6. Django框架(3.django设计模型类、模型类生成表、ORM框架简介)

    ORM框架简介 O是object,也就类对象的意思, R是relation,翻译成中文是关系,也就是关系数据库中数据表的意思, M是mapping,是映射的意思.在ORM框架中,它帮我们把类和数据表进 ...

  7. python数据库框架_Python数据库及ORM框架对比选择

    使用Python进行MySQL的库主要有三个: Python-MySQL(更熟悉的名字可能是MySQLdb), PyMySQL SQLAlchemy. Python-MySQL: 资格最老,核心由C语 ...

  8. .NET框架设计—常被忽视的框架设计技巧

    阅读目录: 1.开篇介绍 2.元数据缓存池模式(在运行时构造元数据缓存池) 2.1.元数据设计模式(抽象出对数据的描述数据) 2.2.借助Dynamic来改变IOC.AOP动态绑定的问题 2.3.元数 ...

  9. Android ORM 框架之 greenDAO 使用心得

    前言 我相信,在平时的开发过程中,大家一定会或多或少地接触到 SQLite.然而在使用它时,我们往往需要做许多额外的工作,像编写 SQL 语句与解析查询结果等.所以,适用于 Android 的ORM  ...

最新文章

  1. Boost--Graph
  2. 求点到直线的最短距离及垂足
  3. matlan数据分析库函数
  4. SAP Word template出了错误后的调试办法
  5. 全自动迁移数据库的实现 (Fluent NHibernate, Entity Framework Core)
  6. python参数_python参数的介绍
  7. matlab函数算错,函数是这个样子的,我是不懂应该怎么输入,试了好多种情况都是错...
  8. linux命令ps aux|grep xxx
  9. 不会写代码也可以, 手把手教你制作炫酷生日祝福网页(程序员专属情人节表白网站)
  10. php考勤管理系统论文,基于PHP的高职院校学生考勤管理系统的研究
  11. 汉英词典python
  12. sdn主要包含哪些接口_SDN概述
  13. 【新书推荐】【2020】电力系统优化问题的方法、算法及MATLAB程序
  14. 中望3d快捷键命令大全_中望CAD快捷键全集
  15. opencv-3.0.0-beta和opencv2版本的区别
  16. Allegro PCB Design GXL (legacy) - 设置旋转角度的快捷键
  17. 台电TBOOK16PRO安装凤凰安卓系统
  18. GNN-CS224W: 6-7 Graph Neural Networks
  19. Windows Server 2019存储池配置
  20. python基于PHP+MySQL的学生社团管理系统

热门文章

  1. 瑞银分析师:4月份iPhone在华销量明显好转 同比仅下滑3%
  2. html5圆圈,HTML5圆形进度条特效代码
  3. 2021年危险化学品经营单位主要负责人最新解析及危险化学品经营单位主要负责人考试试卷
  4. 【随记】Python:前端表格获取到的填写数据插入到数据库表格中数据类型问题
  5. 《动手学深度学习》 环境配置成功经验
  6. 快手ksjsb普通版cookies提取每日低保
  7. 性价比超高,铁威马F4-423(4G)正式上线!
  8. 电商项目测试实战(一)
  9. Vulnhub Empire Breakout
  10. vulnhub Troll1