轻量级DAO层实践初体验

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

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

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

1. 传统 JDBC 实现过程

  • 无论你项目中使用的是什么样的 ORM 框架[Hibernate/MyBatis.....],或者是现在大热的领域模型 DSL DAO层,都是在传统 JDBC的基础上进行的封装。
  • 骚年如果你上手就是 DAO 层框架,没有见过上述的几个家伙的话,建议你反编译 JDK去和他们打个招呼。
  • 传统 JDBC 实现获取数据库数据的代码(随上图序列图)
            //getConnection
            String url = "jdbc:mysql://localhost/db_nxwb?useUnicode=true&characterEncoding=UTF-8";
            String userName = "root";
            String pwd = "123456";
            Connection conn = DriverManager.getConnection(url, userName, pwd);

            //创建 prepareStatement/createStatement
            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 访问数据库的方式是最高效的,没有之一,好比是高级编程语言的效率永远不可能高于底层汇编语言是一样的道理。

现在五花八门的 DAO 层封装就是高级编程语言。

但是 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 EjbService extends BaseVO<EjbService>{
    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<EjbService> ejbServiceList = selecter.find(EjbService.class, map, connection);

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

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

时间: 2024-11-05 14:59:34

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

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

本博客为原创:综合 尚硅谷(http://www.atguigu.com)的系统教程(深表感谢)和 网络上的现有资源(博客,文档,图书等),资源的出处我会标明 本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 ---------------------------------

Spring学习初体验

一.首先我们要明确我们为什么要学?在我们学习了javaweb基础后,我们基本可以完成基本的网站要求,但是我们为什么要学习和使用使用Spring? 1.降低组件之间的耦合度,实现软件各层之间的解耦. controller----->service--------->dao 2.可以使用容器几桶的众多服务.如:事务管理服务.消息服务.当我们使用容器管理时,开发人员就不再需要手工控制事务,也不需处理复杂的事务传播. 3.容器提供单例模式支持,开发人员不再需要自己编写实现代码. 4.容器提供了AOP技

SpringBoot初体验及原理解析

一.前言 ? 我们来用SpringBoot开始HelloWorld之旅.SpringBoot是Spring框架对"约定大于配置(Convention over Configuration)"理念的最佳实践.SpringBoot应用本质上就是一个基于Spring框架的应用.我们大多数程序猿已经对Spring特别熟悉了,那随着我们的深入挖掘,会发现SpringBoot中并没有什么新鲜事,如果你不信,那就一起走着瞧呗! 二.SpringBoot初体验 首先,我们按照下图中的步骤生成一个Spr

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

Docker深入浅出系列 | 容器初体验 教程目标 Docker已经上市很多年,不是什么新鲜事物了,很多企业或者开发同学以前也不多不少有所接触,但是有实操经验的人不多,本系列教程主要偏重实战,尽量讲干货,会根据本人理解去做阐述,具体官方概念可以查阅官方文档,本章目标如下: 了解什么是Docker 了解Docker解决了什么 了解什么是镜像和容器 了解容器与虚拟机的区别 了解Vagrant与Docker的区别 了解Docker引擎和架构 了解Docker的镜像分层 了解VirturalBox和Do

leaflet开源地图库源码阅读笔记(一)——简介&amp;Demo初体验(by vczero)

一.简介 电子地图已经在O2O.生活服务.出行等领域布局,传统的GIS也孕育着互联网基因.传统GIS的图商有ESRI(Arc系列).四维.高德.超图.中地等等.在web互联网领域,百度和高德较为出色.但是开放源代码都远远做的不够,相比而言国外开源较多且丰富,最近认真研读了openlayers2/3和leaflet的源码,收获颇多,不仅加强对浏览器兼容性.HTML5.CSS3等基础,还增强了GIS的基础.本人现在也在一家互联网公司做js api的研发,因此,需要不断地吸取开源代码的营养. leaf

Java高并发秒杀API之业务分析与DAO层

课程介绍 高并发和秒杀都是当今的热门词汇,如何使用Java框架实现高并发秒杀API是该系列课程要研究的内容.秒杀系列课程分为四门,本门课程是第一门,主要对秒杀业务进行分析设计,以及DAO层的实现.课程中使用了流行的框架组合SpringMVC+Spring+MyBatis,还等什么,赶快来加入吧! 第1章 课程介绍 本章介绍秒杀系统的技术内容,以及系统演示.并介绍不同程度的学员可以学到什么内容. 第2章 梳理所有技术和搭建工程 本章首先介绍秒杀系统所用框架和技术点,然后介绍如何基于maven搭建项

bash编程初体验之for

bash编程初体验之for for while until 概述 本文将介绍以for为代表的循环语句在shell 脚本中的应用,常见的循环语句有for, while, until,作为循环语句,顾名思义,它就是重复地做一件事,直到满足某一条件而退出:另外,还有两个循环控制语句continue与break来配合循环语句,以实现临时中断或跳出循环的功能:以下为for, while, until的知识点提炼: for, while, until 进入条件          for: 列表元素非空   

安全初体验

最近尝试在几个高校跟各个学生来讲安全入门的一些东西,我把这个称谓安全初体验,我说是从抓肉鸡开始,但是讲完后,同学们都一脸茫然,不知道我说的是什么,可能是我的入口点没有找对,那么我现在就将网络上以及一些自己整理的安全入门的东西分享给大家,希望能够对想进入这个行业的同学一点帮助吧. 首先进入这个行业,我们需要一个背影,看着这个背影来不断的成长,给我们精神上的支持和寄托,因为这个行业的门槛相对还是比较高的,有了这个背影,我们才能够在每一个孤单寂寞的夜里,不断的坚持前行,永不放弃.我想每个在这个行业坚持

聚合类新闻客户端初体验

初体验的产品:今日头条(ios3.6).百度新闻(ios4.4.0).ZAKER(ios4.4.5).鲜果(ios3.8.7).中搜搜悦(ios4.0.1).Flipboard(ios2.3.9) 1.Flipboard 一款国外很火的app,UI以及体验都做得非常不错,很多人都评论其不接地气,在我看来,这确实是一方面,另外,大陆防火长城也有一部分原因,毕竟外来的互联网产品很难在国内扎根. 初体验: 1).首次启动加载速度太慢,用户没有那么多的耐心去使用第一次接触的产品: 2).手指上下滑动更换