orm

一个ORM框架的设计思路
在典型的MVC分层模式中,各层之间一般都是通过接口来完成定义和具体实现的解耦,假设我们当前也是基于这种模式,在Model层定义的都是接口。

编辑
1. 需求:一个DAO接口
我们有一个账单类 Bill ,包含id、custId(用户ID)、billDay(账单日)三个成员变量; 一个对应的t_clbs_bill表, 包含id、cust_id、bill_day三列.

现在,我们有这样一个账单 Bill 的DAO接口,包含一个简单的根据用户ID和账单日查询账单对象的方法,这个方法仅仅演示查询(select)功能。相对于增、删、改类型的SQL语句只需要返回所影响行数来说,SELECT语句更能全面展示一个完整的JDBC访问数据库过程。

/**
* Bill数据访问层接口
*/
public interface BillDao {
/**
* 通过用户ID和账单日查询账单
*
* @param custId 用户ID
* @param billDay 账单日: yyyyMMdd
* @return 账单对象
*/
Bill findByCustIdAndBillDay(String custId, String billDay);
}
编辑
2. 使用Java提供的JDBC API实现
首先我们需要开始实现这个接口,在没有ORM框架的时候, 我们需要使用最基础的JDBC来实现。

public class BillDaoJdbcImpl implements BillDao {
private static final String URL = “jdbc:oracle:thin:@192.168.1.18:1521:d0posb”;
private static final String USERNAME = “username”;
private static final String PWD = “passwordXXX”;
private static final String SQL = “SELECT * FROM t_clbs_bill b WHERE b.cust_id=? AND b.bill_day=?”;

@Override
public Bill findByCustIdAndBillDay(String custId, String billDay) {
    List<Bill> bills = new ArrayList<Bill>();
    Connection connection = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;
    try {
        Class.forName("oracle.jdbc.driver.OracleDriver");               // 1. 加载JDBC驱动
        connection = DriverManager.getConnection(URL, USERNAME, PWD);   // 2. 获取数据库连接
        stmt = connection.prepareStatement(SQL);                        // 3. 给定SQL创建statement
        stmt.setString(1, custId);                                      // 4. 参数赋值
        stmt.setString(2, billDay);
        rs = stmt.executeQuery();                                       // 5. 执行SQL语句
        while (rs.next()) {                                             // 6. 结果集到对象的映射
            Bill bill = new Bill();
            bill.setId(rs.getLong("id"));
            bill.setCustId(rs.getString("cust_id"));
            bill.setBillDay(rs.getString("bill_day"));
            bills.add(bill);
        }
        if (bills.size() > 1) {
            throw new SQLException("more than 1 result");
        }
        return bills.get(0);
    } catch (Exception e) {
        // ...
    } finally {
        try {                                                           // 7. 关闭资源
            if (connection != null) {
                connection.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (rs != null) {
                rs.close();
            }
        } catch (SQLException e) {
            // ...
        }
    }
    return null;
}

}
使用JDBC访问数据库大体需要经过以上代码注释中标注的7个步骤。
可以看到只有第4步参数赋值、第6步结果集到对象的映射是和我们的实际业务是相关的,大部分的步骤都是重复的,作为客户端(service层)代码只需要提供:1,SQL语句;2,参数绑定方法;3,结果集的映射方法;然后把通用的步骤和方法封装成单独的一个工具方法即可。因此考虑使用设计模式,将通用部分的代码抽象出来。
对于遵循通用步骤的代码,有三种设计模式比较适合这样的场景:模板模式、策略模式和代理模式。

编辑
3.第一层抽象:使用模板模式定义固定的步骤
我们定义一个SQL执行模板,在主方法中定义执行SQL的模板,即通用的步骤;对于不同的SQL,其参数绑定、结果集解析这些放到具体的子类中去实现,从而减少了大量重复的代码,模板类如下:

public abstract class SqlExecutorTemplate {
/**
* 获取数据库连接, 可以直接创建Connection, 也可以通过JNDI、数据库连接池等方式
*/
public abstract Connection getConnection();
/**
* 参订Java对象到SQL参数
*/
public abstract void bind(PreparedStatement statement, T paramObject);
/**
* 解析结果集到指定的对象
*/
public abstract T handleResultSet(ResultSet resultSet, Class returnType);

public T execute(String sql, T paramObject, Class<T> returnType) {
    Connection connection = null;
    PreparedStatement stmt = null;
    ResultSet rs = null;
    try {
        connection = this.getConnection();
        stmt = connection.prepareStatement(toJDBCSql(sql));
        this.bind(stmt, paramObject);
        rs = stmt.executeQuery();
        return this.handleResultSet(rs, returnType);
    } catch (Exception e) {
    } finally {
        try {
            if (connection != null) { connection.close(); }
            if (stmt != null) { stmt.close(); }
            if (rs != null) { rs.close(); }
        } catch (SQLException e) {}
    }
    return null;
}

定义好抽象的模板类之后, 我们就可以在具体的DAO实现类中,只需要向模板类传递本地查询所需要的参数(SQL语句、参数绑定、结果集解析)即可:

public class BillDaoJdbcImpl2 implements BillDao {
@Override
public Bill findByCustIdAndBillDay(Bill bill) {
String SQL = “SELECT * FROM t_clbs_bill b WHERE b.cust_id=? AND b.bill_day=?”;
return new SqlExecutorTemplate() {
@Override
public Connection getConnection() {
return null;
}
@Override
public void bind(PreparedStatement statement, Bill paramObject) {}

        @Override
        public Bill handleResultSet(ResultSet resultSet, Class<Bill> returnType) {
            return null;
        }
    }.execute(SQL, bill, Bill.class);
}

}
其中getConnection是系统级的服务, 是何具体业务无关的对于具体的Dao实现类可以不用关心,因此可以进一步抽象出来;
对于Statement的处理, 常规的查询推荐使用使用预编译语句PreparedStatement的;参数绑定和结果集解析可以参见之前使用JDBC查询的过程是一样的。
最后一步对于结果集的处理,只有在查询SELECT类型才需要,而对于INSERT、UPDATE、DELETE类型的SQL语句返回的是影响的结果集数量, 因此对于不同类型的SQL所执行的结果集解析策略是不一样的;

编辑
4.第二层抽象:外化ORM映射策略 经过使用模板模式抽象后,具体的DAO实现类只需要实现SqlExecutorTemplate抽象类的三个抽象方法,而无需再重复执行繁琐的JDBC查询过程。对于这三个抽象方法:
getConnection(),在实践中获取数据库连接目前主要有JDNI、从数据源DataSource,至于使用哪一种方式一般是系统级别确定的,对于所有的DAO实现类都是通用的,因此我们在实现DAO接口的时候无需再考虑,所以可以直接为SqlExecutorTemplate实现类提供Connection实例;
bind()方法用来为PreparedStatement赋值,实现从Java对象到SQL参数的映射;Java对象和数据库表的列是一一对应的,因此我们可以定义这样一个双向映射表,可以通过Java类名和字段名唯一确定一个表的列,也可以通过表名和列名唯一确定一个Java Bean的成员变量,我们将这种映射关系保存在一个可编辑的文本文件中,比如XML文件:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值