轻量级DAO层实践初体验
最近快被 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层实践初体验的更多相关文章
- [原创]java WEB学习笔记21:MVC案例完整实践(part 2)---DAO层设计
		
本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当 ...
 - Spring之初体验
		
Spring之初体验 Spring是一个轻量级的Java Web开发框架,以IoC(Inverse of Control 控制反转)和 ...
 - 阿里 RPC 框架 DUBBO 初体验
		
最近研究了一下阿里开源的分布式RPC框架dubbo,楼主写了一个 demo,体验了一下dubbo的功能. 快速开始 实际上,dubbo的官方文档已经提供了如何使用这个RPC框架example代码,基于 ...
 - 【Knockout.js 学习体验之旅】(1)ko初体验
		
前言 什么,你现在还在看knockout.js?这货都已经落后主流一千年了!赶紧去学Angular.React啊,再不赶紧的话,他们也要变out了哦.身旁的90后小伙伴,嘴里还塞着山东的狗不理大蒜包, ...
 - SpringBoot初体验及原理解析
		
一.前言  上篇文章,我们聊到了SpringBoot得以实现的幕后推手,这次我们来用SpringBoot开始HelloWorld之旅.SpringBoot是Spring框架对“约定大于配置(Conv ...
 - ThinkPHP -- 开发初体验及其几个配置文件的介绍
		
ThinkPHP是一款不错的轻量级的PHP+MVC框架,它吸取了Ruby On Rails的特性,不仅将Model.View.Controller分开,而且实现了ORM.模板标签等高级特性. 开 ...
 - 使用Unitils测试DAO层
		
Spring 的测试框架为我们提供一个强大的测试环境,解决日常单元测试中遇到的大部分测试难题:如运行多个测试用例和测试方法时,Spring上下文只需创建一次:数据库现场不受破坏:方便手工指定Sprin ...
 - Handlebars的基本用法   Handlebars.js使用介绍  http://handlebarsjs.com/    Handlebars.js 模板引擎  javascript/jquery模板引擎——Handlebars初体验   handlebars.js 入门(1)  作为一名前端的你,必须掌握的模板引擎:Handlebars  前端数据模板handlebars与jquery整
		
Handlebars的基本用法 使用Handlebars,你可以轻松创建语义化模板,Mustache模板和Handlebars是兼容的,所以你可以将Mustache导入Handlebars以使用 Ha ...
 - Docker深入浅出系列 | 容器初体验
		
目录 Docker深入浅出系列 | 容器初体验 教程目标 预备工作 容器与虚拟化技术 什么是Docker 为什么要用Docker 事例 什么是容器镜像和容器 容器与虚拟机的区别 Vagrant与Doc ...
 
随机推荐
- 使用 Arduino 和 LM35 温度传感器监测温度
			
上一篇玩儿了一下Arduino入门,这次再进一步,用一下LM35温度传感器来监测当前温度.LM35温度传感器已经在Arduino入门套件里包含了,就是那个有三个脚的小黑块儿. 我们先把这些东西连起来. ...
 - SharePoint 2007 Full Text Searching PowerShell and CS file content with SharePoint Search
			
1. Ensure your site or shared folder in one Content Source. 2. Add file types. 3. The second step in ...
 - Android Fragment使用(三) Activity, Fragment, WebView的状态保存和恢复
			
Android中的状态保存和恢复 Android中的状态保存和恢复, 包括Activity和Fragment以及其中View的状态处理. Activity的状态除了其中的View和Fragment的状 ...
 - asp.netDataTable导出excel方法(1)
			
先来写一段代码,这段代码也是我在网上找的,但是他那个原先有点问题,我对他那个进行了修改,现在这个代码是我修改改过的,应该没有问题的. public int StreamExport(System.Da ...
 - Android 手机卫士--设置密码对话框
			
本文实现初次设置密码验证过程,首先实现如下效果 本文地址:http://www.cnblogs.com/wuyudong/p/5939823.html,转载请注明出处. 布局如下: <?xml ...
 - android滚动公告栏
			
项目里要用到开奖公告,单行显示向上滚动的TextView,网上随便找了一个控件发现效果还不错改装一下就可以用到项目里.唯一不妥的地方就是字体大小不太好控制,不是正常的字体大小,也没有深究代码,先把工作 ...
 - Android 热修复技术(1)---原理
			
热修复技术分为几部分: 原理介绍 Android HotFix源码分析 自定义框架 1.Android分包MultiDex原理 首先Dex是什么东西? Dex就是Window里面的exe文件 也就是可 ...
 - SqlServer环境配置和卸载
			
一.数据库简介 SQLServer环境配置 安装好数据库以后怎么启用sa账号,来访问数据库. 1.先用windows账号登录数据库. 2.启用windows身份验证方式和sql server身份验证方 ...
 - Hive 分组问题
			
group by 中出现的字段不能再select 后面单独显示,必须配合函数使用 上面中的 ' group by id 总结: Hive不允许直接访问非group by字段: 对于非group by字 ...
 - ligerDialog的使用
			
1.通过ViewBag来传值. @if (ViewBag.ReturnMessage != null) 2.脚本代码: 对话框设计与赋值问题. <script type="text/j ...