【问题标题】:DAO Design Pattern vs DBUnit CodeDAO 设计模式与 DBUnit 代码
【发布时间】:2015-04-29 21:07:57
【问题描述】:

我不确定我的 DAO 或 DBUnit 测试是否需要重构。有人可以指导我面临的情况吗?

我有一个 DAO,它通过获取 Connection 对象从数据库中获取一些数据,然后关闭所有资源(ResultSetStatementConnection)。

我正在使用 DBUnit 框架测试这个 DAO,在这里我在这个 DBUnit 测试中执行两个 DB 操作: 1) 创建一个表,然后从 XML 数据集文件加载数据 2) 测试DAO中实际存在的getDataById(int id)方法

问题是我的第二个getDataById(id) 方法无法获取数据库连接对象,因为我的DAO 在执行上述步骤1 时已经关闭了它。

实际释放所有资源的代码sn-p如下所示。

DAO.java(代码 sn-p)

public void releaseResources(ResultSet rs, Statement stmt, Connection cn) {
        System.out.println("rs = " + rs);
        System.out.println("stmt = " + stmt);
        System.out.println("cn = " + cn);
        try {
            if (rs != null) {
                rs.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (cn != null) {
                cn.close();
            }
        } catch (SQLException ex) {
            System.out.println("Exception while closing DB resources rs, stmt or cn......." + ex);
        }
    }

因此,为了让我的 DBUnit 测试正常工作,我必须重载上面的 releaseResources() 方法,以便它不会关闭可以在我的getDataById(int id) 单元测试中使用的连接对象。如下所示。

**DAO.java(带有重载方法的代码sn-p)**

//OVERLOADED JUST TO GET DBUNIT TESTS WORKING !!! :(
public void releaseResources(Statement stmt) {
    System.out.println("\nReleasing db resources now.... 1111");
    System.out.println("stmt = " + stmt);
    try {
        if (stmt != null) {
            stmt.close();
        }
    } catch (SQLException ex) {
        System.out.println("Exception while closing DB resources stmt......." + ex);
    }
}

我不确定这是否是正确的设计。有人可以指导我如何改进吗?

完整代码如下所示,供进一步参考。

StateDaoTest.java

public class StateDaoTest {

    protected static Connection connection;
    protected static HsqldbConnection dbunitConnection;
    protected static StateDao dao = new StateDao();

    @BeforeClass
    public static void setupDatabase() throws Exception {
        Class.forName("org.hsqldb.jdbcDriver");
        connection = DriverManager.getConnection("jdbc:hsqldb:mem:my-project-test;shutdown=true");
        dbunitConnection = new HsqldbConnection(connection, null);
    }

    @Before
    public void createTable() throws SQLException {
        dao.setConnection(connection);
        dao.createTables();
    }

    protected IDataSet getDataSet(String name) throws Exception {
        InputStream inputStream = getClass().getResourceAsStream(name);
        assertNotNull("file" + name + " not found in classpath", inputStream);
        Reader reader = new InputStreamReader(inputStream);
        FlatXmlDataSet dataset = new FlatXmlDataSet(reader);
        return dataset;
    }

    @AfterClass
    public static void closeDatabase() throws Exception {
        System.out.println("\ninto the closeDatabase() method...");
        System.out.println("connection = " + connection);
        System.out.println("dbunitConnection = " + dbunitConnection);
        if (connection != null) {
            connection.close();
            connection = null;
        }
        if (dbunitConnection != null) {
            dbunitConnection.close();
            dbunitConnection = null;
        }
    }

    @Test
    public void testGetStateById() throws Exception {
        IDataSet setupDataSet = getDataSet("/states.xml");
        DatabaseOperation.CLEAN_INSERT.execute(dbunitConnection, setupDataSet);
        State state = dao.getStateById(1);
        assertNotNull(state);
        assertEquals("Pennsylvania", state.getName());
        assertEquals("PA", state.getStateCode());
        assertNotNull(state.getTaxPct());
        assertEquals("Y", state.getActive());
    }

}

StateDao.java

public class StateDao {

    private Connection connection;

    public void setConnection(Connection connection) {
        this.connection = connection;
    }

    //added for dbunit tests
    public void createTables() throws SQLException {
        String sql = "CREATE TABLE states (stateId INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1), "
                + "stateCd VARCHAR(10), name VARCHAR(20), taxPct NUMERIC, active CHAR(1))";
        Statement stmt = null;
        try {
            stmt = connection.createStatement();
            stmt.execute(sql);
        } finally {
            releaseResources(stmt);
        }
    }

    public State getStateById(long id) {
        String sql = "SELECT * FROM states WHERE stateId = " + id;

        Statement stmt = null;
        ResultSet rs = null;
        State state = null;

        System.out.println(sql);

        try {
            stmt = connection.createStatement();
            rs = stmt.executeQuery(sql);
            while (rs != null && rs.next()) {
                String stateId = StringUtils.defaultString(rs.getString("stateId"));
                String stateCd = StringUtils.defaultString(rs.getString("stateCd"));
                String name = StringUtils.defaultString(rs.getString("name"));
                String taxPct = StringUtils.defaultIfEmpty(rs.getString("taxPct"), "0");
                String active = StringUtils.defaultString(rs.getString("active"));
                state = new State(new Integer(stateId), stateCd, name, new BigDecimal(taxPct), active);
                System.out.println("state = " + state);
            }
            System.out.println("state = " + state);
        } catch (SQLException ex) {
            System.out.println("Exception whiile fetching data for a state......." + ex);
        } finally {
            releaseResources(rs, stmt, connection);
        }
        return state;
    }

    public void releaseResources(ResultSet rs, Statement stmt, Connection cn) {
        System.out.println("\nReleasing db resources now....2222");
        System.out.println("rs = " + rs);
        System.out.println("stmt = " + stmt);
        System.out.println("cn = " + cn);
        try {
            if (rs != null) {
                rs.close();
            }
            if (stmt != null) {
                stmt.close();
            }
            if (cn != null) {
                cn.close();
            }
        } catch (SQLException ex) {
            System.out.println("Exception while closing DB resources rs, stmt or cn......." + ex);
        }
    }

    //added for dbunit tests
    public void releaseResources(Statement stmt) {
        System.out.println("\nReleasing db resources now.... 1111");
        System.out.println("stmt = " + stmt);
        try {
            if (stmt != null) {
                stmt.close();
            }
        } catch (SQLException ex) {
            System.out.println("Exception while closing DB resources stmt......." + ex);
        }
    }  

}

【问题讨论】:

    标签: java design-patterns junit dao dbunit


    【解决方案1】:

    我会继续使用重载的releaseResources 方法。如果连接是从外部传入的,那么 dao 不拥有它,也不应该关闭它。相反,连接应该由创建它的同一个类单独关闭。所以getStateById应该改成只关闭resultSet和statement,连接应该在别处关闭。

    您可以添加额外的 releaseResources 重载来获取结果集和语句,或者如果您使用的是 Java 7 - 您可以创建一个方法:releaseResouces(Closeable... closeables)。我会将此方法设为单独类的公共静态方法,以便任何类都可以使用它。

    【讨论】:

      【解决方案2】:

      另一种选择是使用两个连接 - 一个用于生产代码,一个用于 dbUnit。

      这与 DAO 如何获取连接无关 - 方法参数、DI、查找(例如 JNDI)。我经常配置用于生产代码的数据源和用于 dbUnit 的简单 JDBC 连接。

      使用两个连接也更安全,因为它会强制生产代码在 dbUnit 连接可以看到之前完成/提交。这确实证明了生产代码有效。

      【讨论】:

      • 好的,我明白你在说什么,但是如果我想测试多个 DAO 方法,例如 getProductById(int id)getAllProducts() in the same dbunit test。第一次方法调用后连接不会关闭吗?我是否必须通过在 dbunit 测试用例中的某处定义 getConnection() 然后调用它来重新打开连接。您能否举例说明如何实现这一目标?每次执行测试方法时,我都会犹豫是否打开新的数据库连接。
      • 您的生产连接有问题。这就是你的 DAO 的设计:它在构造函数中接收一个打开的连接,然后在第一次使用后关闭它。测试不是问题;测试显示生产代码问题。为 dbUnit 建立第二个连接意味着该连接在测试关闭之前不会关闭。
      • 是的,我同意实际的 DAO 设计才是这里的问题。您能否提供一些代码示例来说明如何重构/重新设计此 DAO 以使其更具单元测试性?
      猜你喜欢
      • 2012-04-14
      • 2013-07-12
      • 1970-01-01
      • 1970-01-01
      • 2012-08-21
      • 2015-01-26
      • 1970-01-01
      • 2015-12-28
      • 1970-01-01
      相关资源
      最近更新 更多