最近快被 Hibernate 给坑哭了,有了自己动手实现 ORM 映射 DAO 的冲动。

工作之余折腾了快一星期,总算是有点小成就。

现打算将过程记录下来,方便自己后续回顾填补遗漏。

Github 地址:https://github.com/OrsonEx/sdao.git

1. 传统 JDBC 实现过程

  • 无论你项目中使用的是什么样的 ORM 框架[Hibernate/MyBatis.....],或者是现在大热的领域模型 DSL DAO层,都是在传统 JDBC的基础上进行的封装。
  • 骚年如果你上手就是 DAO 层框架,没有见过上述的几个家伙的话,建议你反编译 JDK去和他们打个招呼。
  • 传统 JDBC 实现获取数据库数据的代码(随上图序列图)
            //getConnection
String url = "jdbc:mysql://localhost/db_school?useUnicode=true&characterEncoding=UTF-8";
String userName = "root";
String pwd = "123456";
Connection conn = DriverManager.getConnection(url, userName, pwd); //创建 createStatement/prepareStatement
String sqlStatement = "select * from tmp_services where uuid ='c9ea709cb30d4954a33dfec01d3ef142'";
Statement statement = conn.createStatement(); String sqlPrepared = "select * from tmp_services where uuid = ?";
PreparedStatement preparedStatement = conn.prepareStatement(sqlPrepared);
preparedStatement.setString(1, "c9ea709cb30d4954a33dfec01d3ef142"); //executeQuery
ResultSet resultSetStatement = statement.executeQuery(sqlStatement);
ResultSet resultSetPrepared = preparedStatement.executeQuery(); //遍历结果
while (resultSetPrepared.next()) {
System.out.println("on:" + resultSetPrepared.getRow());
System.out.println("uuid:" + resultSetPrepared.getString("uuid") + ",name:" + resultSetPrepared.getString("name"));
}
  • PreparedStatement/Statement 提供的两种不同的运行 执行Sql 事物对象。
  • PreparedStatement 为预编译 Sql,执行前已经被编译,DBMS 只需执行即可,这就意味这 这种形式的 Sql 语句执行效率相当高。
  • Statement  为执行时才会进行编译Sql,然后被 DBMS 执行,所以这个对象在执行 Sql 不是很频繁时,相对不错。
  • PreparedStatement 效率高,还可以有效的防止 Sql 注入,但会占用多余的内存空间(用于预编译)。
  • 就好像是胸大,臀漂亮的妹子,脸不好看,你需要牺牲一部分东西,去换取另外一部分东西,没有绝对完美的选择。

2. 基于注解的 ORM 实践

JDBC 访问数据库的方式是最高效的,没有之一,好比是高级编程语言的效率永远不可能高于底层汇编语言是一样的道理。

但是 JDBC 对于程序员来说太难驾驭了,没办法将关系数据库中的数据抽象到 Java的面向对象的世界。

下面是我业余时间在摸索出来的不依靠任何框架,只使用 JDK自带的注解和反射的 DAO 层实现,

其中还有很多的问题,但是大体的样子已经很可爱了。

a.定义你自己的 DAO 表名注解

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Inherited
public @interface TableName {
String value();
}

b.在 POJO (与数据库表结构对应) 中是添加 TableName 注解

@TableName("tmp_services")
public class Service extends BaseVO<Service>{
private String id;
private String name;
private String theme;
private String type;
private String descrition;
private String XSD;
private String remark;
..........
}

d.获取注解表名、组织 Sql 、调整姿势

    public <T> List<T> find(Class<T> clazz, Map param, Connection connection) throws DataOptException {
TableName tableName = clazz.getAnnotation(TableName.class);
if (tableName == null) {
throw new DataOptException("A1-308", "没有找到类[" + clazz.getName() + "]所映射的表!");
} else {
String sql = "SELECT * FROM " + tableName.value() + " WHERE 1=1 ";
ArrayList paras = new ArrayList(); if (param != null) {
Iterator<Map.Entry<String, Object>> iterator = param.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, Object> entry = iterator.next();
sql = sql + " AND " + entry.getKey() + " = ?";
paras.add(entry.getValue());
}
}
return this.select(connection, sql, paras, clazz);
}
}

e.熟悉的JDBC 操作、获取查询结果集、反射填充POJO属性

public <T> List<T> select(Connection connection, String sql, List<Object> paras, Class<T> clazz) throws DataOptException {
ArrayList retList = new ArrayList();
try {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
Object bean;
Iterator iterator;
if (paras != null) {
int count = 1;
iterator = paras.iterator(); while (iterator.hasNext()) {
bean = iterator.next();
preparedStatement.setObject(count++, bean);
}
} ResultSet resultSet = preparedStatement.executeQuery(); String fname;
Object value;
List fields = getCanWriteField(clazz);
Iterator filedIter = fields.iterator(); Field field;
while (filedIter.hasNext()) {
field = (Field) filedIter.next();
field.setAccessible(true); //抑制java 对修饰符的检查
} HashSet hashSet = new HashSet();
ResultSetMetaData metaData = resultSet.getMetaData(); int i;
for (i = 1; i <= metaData.getColumnCount(); ++i) {
hashSet.add(metaData.getColumnLabel(i).toLowerCase());
} for (i = fields.size() - 1; i >= 0; --i) {
if (!hashSet.contains(((Field) fields.get(i)).getName().toLowerCase())) {
fields.remove(i);
}
} for (; resultSet.next(); retList.add(bean)) {
bean = clazz.newInstance();
filedIter = fields.iterator(); while (filedIter.hasNext()) {
field = (Field) filedIter.next();
fname = field.getName();
if (ClassUtils.isAssignable(field.getType(), Date.class)) {
Timestamp timestamp = resultSet.getTimestamp(fname);
if (timestamp != null) {
field.set(bean, timestamp);
}
continue;
} value = resultSet.getObject(fname);
if (value != null) {
try {
field.set(bean, reflect(field.getType(), value));
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
} catch (Throwable e) {
throw new DataOptException("A1-001", e.getMessage(), e);
} finally {
this.closeConnection(connection);
}
return retList;
}

f.  将 ResultSet 获取的值 转换 POJO 属性时,判断逻辑稍微多一点,基本囊括了主流的 java 基本类型...

 public static Object changeType(Class propType, Object tmpobj) throws ParseException {
if (propType.isInstance(tmpobj)) {
return tmpobj;
} else {
String value = toString(tmpobj, "").trim();
if (propType.equals(Date.class)) {
return value.length() == 0 ? null : (value.length() == 8 ? Keys.df3.parse(value) : (NumberUtils.isNumber(value) ? new Date(Long.parseLong(value) * 86400000L + Keys.df4.parse("1900-01-01").getTime()) : (value.length() == 10 ? Keys.df4.parse(value) : (value.length() <= 16 ? Keys.df5.parse(value) : Keys.df7.parse(value)))));
} else if (propType.equals(java.sql.Date.class)) {
return value.length() == 0 ? null : (value.length() == 8 ? new java.sql.Date(Keys.df3.parse(value).getTime()) : (NumberUtils.isNumber(value) ? new java.sql.Date(Long.parseLong(value) * 86400000L + Keys.df4.parse("1900-01-01").getTime()) : (value.length() <= 10 ? new java.sql.Date(Keys.df4.parse(value).getTime()) : new java.sql.Date(Keys.df4.parse(value.substring(0, 10)).getTime()))));
} else if (propType.equals(Timestamp.class)) {
return value.length() == 0 ? null : (value.length() == 8 ? new Timestamp(Keys.df3.parse(value).getTime()) : (NumberUtils.isNumber(value) ? new Timestamp(Long.parseLong(value) * 86400000L + Keys.df4.parse("1900-01-01").getTime()) : (value.length() <= 10 ? new Timestamp(Keys.df4.parse(value).getTime()) : (value.length() <= 16 ? new Timestamp(Keys.df5.parse(value).getTime()) : new Timestamp(Keys.df7.parse(value).getTime())))));
} else if (propType.equals(Calendar.class)) {
Date pd = (Date) changeCast(Date.class, tmpobj);
if (pd != null) {
Calendar cal = Calendar.getInstance();
cal.setTime(pd);
return cal;
} else {
return null;
}
} else if (propType.equals(String.class)) {
return value;
} else if (!propType.equals(Character.class) && !propType.equals(Character.TYPE)) {
if (!propType.equals(Byte.class) && !propType.equals(Byte.TYPE)) {
if (!propType.equals(Short.class) && !propType.equals(Short.TYPE)) {
if (!propType.equals(Integer.class) && !propType.equals(Integer.TYPE)) {
if (!propType.equals(Long.class) && !propType.equals(Long.TYPE)) {
if (!propType.equals(Float.class) && !propType.equals(Float.TYPE)) {
if (!propType.equals(Double.class) && !propType.equals(Double.TYPE)) {
if (!propType.equals(Boolean.class) && !propType.equals(Boolean.TYPE)) {
System.out.println("无法转换为内部表示 \'" + propType.getName() + "\' 对象不可用");
return tmpobj;
} else {
return Boolean.valueOf(value.trim().length() == 0 ? false : (new Boolean(value)).booleanValue());
}
} else {
return Double.valueOf("NaN".equals(value) ? 0.0D : (value.trim().length() == 0 ? 0.0D : (new Double(value)).doubleValue()));
}
} else {
return Float.valueOf("NaN".equals(value) ? 0.0F : (value.trim().length() == 0 ? 0.0F : (new Float(value)).floatValue()));
}
} else {
return value.length() == 0 ? Long.valueOf(0L) : Long.valueOf("NaN".equals(value) ? 0L : (value.contains(".") ? new Long(value.split("\\.")[0]) : new Long(value)).longValue());
}
} else {
return value.length() > 0 ? Integer.valueOf("NaN".equals(value) ? 0 : (value.contains(".") ? new Integer(value.split("\\.")[0]) : new Integer(value)).intValue()) : Integer.valueOf(0);
}
} else {
return Short.valueOf(value.length() > 0 ? (new Short(value)).shortValue() : 0);
}
} else {
return new Byte(value);
}
} else {
return Character.valueOf(value.length() > 0 ? (new Character(value.charAt(0))).charValue() : '\u0000');
}
}
}

d.项目中的使用方式

            Map<String,Object> map = new HashMap<>();
map.put("uuid","c9ea709cb30d4954a33dfec01d3ef142");
List<Service> serviceList = selecter.find(EjbService.class, map, connection);

PO、Map 作为参数,使用方式是不是很简单呢?

只是对查询进行了的封装,后续需要关注下更新和插入、与Spring 的 整合、数据连接池的整合....

轻量级DAO层实践初体验的更多相关文章

  1. [原创]java WEB学习笔记21:MVC案例完整实践(part 2)---DAO层设计

    本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...

  2. Spring之初体验

                                     Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...

  3. 阿里 RPC 框架 DUBBO 初体验

    最近研究了一下阿里开源的分布式RPC框架dubbo,楼主写了一个 demo,体验了一下dubbo的功能. 快速开始 实际上,dubbo的官方文档已经提供了如何使用这个RPC框架example代码,基于 ...

  4. 【Knockout.js 学习体验之旅】(1)ko初体验

    前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...

  5. SpringBoot初体验及原理解析

    一.前言 ​ 上篇文章,我们聊到了SpringBoot得以实现的幕后推手,这次我们来用SpringBoot开始HelloWorld之旅.SpringBoot是Spring框架对“约定大于配置(Conv ...

  6. ThinkPHP -- 开发初体验及其几个配置文件的介绍

    ThinkPHP是一款不错的轻量级的PHP+MVC框架,它吸取了Ruby On Rails的特性,不仅将Model.View.Controller分开,而且实现了ORM.模板标签等高级特性.    开 ...

  7. 使用Unitils测试DAO层

    Spring 的测试框架为我们提供一个强大的测试环境,解决日常单元测试中遇到的大部分测试难题:如运行多个测试用例和测试方法时,Spring上下文只需创建一次:数据库现场不受破坏:方便手工指定Sprin ...

  8. Handlebars的基本用法 Handlebars.js使用介绍 http://handlebarsjs.com/ Handlebars.js 模板引擎 javascript/jquery模板引擎——Handlebars初体验 handlebars.js 入门(1) 作为一名前端的你,必须掌握的模板引擎:Handlebars 前端数据模板handlebars与jquery整

    Handlebars的基本用法 使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Ha ...

  9. Docker深入浅出系列 | 容器初体验

    目录 Docker深入浅出系列 | 容器初体验 教程目标 预备工作 容器与虚拟化技术 什么是Docker 为什么要用Docker 事例 什么是容器镜像和容器 容器与虚拟机的区别 Vagrant与Doc ...

随机推荐

  1. 如何使用github搭建个人博客

    1.去github官网注册个人帐号:没有的:猛戳这里去注册,比如我的账户名:wjf444128852,我的已经汉化(可在github里搜索github如何汉化有插件) 2.点击仓库-新建,仓库名字必须 ...

  2. IE8兼容模式设置

    设置---兼容性视图设置--添加此网站--在IE8中调试(防止调整IE内核后浏览器崩溃,360可通过设置极速模式-兼容模式  点击地址栏绿色图标)

  3. ReactiveCocoa代码实践之-RAC网络请求重构

    前言 RAC相比以往的开发模式主要有以下优点:提供了统一的消息传递机制:提供了多种奇妙且高效的信号操作方法:配合MVVM设计模式和RAC宏绑定减少多端依赖. RAC的理论知识非常深厚,包含有FRP,高 ...

  4. Python聊天室

    小编心语:锵锵锵!各位看官注意了啊,走过路过表错过!上篇博文主要介绍了基于基于Server-Sent Event的简单在线聊天室,相信不管各位是大牛.小牛还是跟小编一样的小白,可能觉得看得不够过瘾,区 ...

  5. [css]我要用css画幅画(三)

    接着之前的[css]我要用css画幅画(二), 今天,我画了一个小人,他的名字暂时叫作小明. 以下只列出本次修改增加的内容 html如下: <div class="human left ...

  6. .NET系列文章——近一年文章分类整理,方便各位博友们查询学习

    由于博主今后一段时间可能会很忙(准备出书:<.NET框架设计—模式.配置.工具>,外加换了新工作),所以博客会很少更新: 在最近一年左右时间里,博主各种.NET技术类型的文章都写过,根据博 ...

  7. Python写地铁的到站的原理简易版

    Python地铁的到站流程及原理(个人理解) 今天坐地铁看着站牌就莫名的想如果用Python写其工作原理 是不是很简单就小试牛刀了下大佬们勿喷纯属小弟个人理解 首先来看看地铁上显示的站牌如下: 就想这 ...

  8. Nova reboot 和 lock 操作 - 每天5分钟玩转 OpenStack(32)

    前面 CloudMan 通过日志详细分析了 nova 的 launch, shut off 和 start 操作.不知道大家现在是否已经掌握了日志分析的技能? 今天咱们就来检验一下.本节讨论的是 no ...

  9. java中System.getenv和System.getProperties的区别

    System.getenv获取的是系统的环境变量(就是用户在操作系统中设置的环境变量),windows和linux下环境变量的设置就不说了哦. System.getProperties获取的是系统的相 ...

  10. java请求https地址如何绕过证书验证?

    原文http://www.blogjava.net/hector/archive/2012/10/23/390073.html 第一种方法,适用于httpclient4.X 里边有get和post两种 ...