2012-07-11 19 views
2

我正在使用JUnit4和AbstractTransactionalJUnit4SpringContextTests,并且想要创建简单的CRUD单元测试 - 如下所示。但是,对SQL更新的调用正在导致事务管理器为我的测试实例生成一个新的ID。由于在更新过程中没有传回新ID,因此我不再拥有测试行的主键 - 这会阻止测试的其余部分。使用JUnit在Spring中测试CRUD时获取同一行的多个ID

有没有办法阻止事务管理器在更新被调用时为本书生成新的ID? (做不到这一点是有没有更好的方法来测试CRUD?)

@Test 
public void testCRUDBook() { 
    Book b1 = new Book(title, author); 
    BookFactory factory = database.getBookFactory(); 
    int id = factory.createBook(b1); 

    Book b2 = factory.readBook(id);   
    assertEquals(b1.getTitle(), b2.getTitle()); 
    assertEquals(b1.getAuthor(), b2.getAuthor()); 

    b2.setTitle("title 2"); 
    b2.setAuthor("author 2"); 
    assertTrue(factory.updateBook(b2)); 

    // The problem arises here as updating the book record causes a new Id 
    // to be generated so querying by Id is no longer possible. 
    Book b3 = factory.readBook(b2.getId());   
    assertEquals(b3.getTitle(), "title 2"); 
    assertEquals(b3.getAuthor(), "author 2");  

    assertTrue(factory.deleteBook(b3)); 
} 

书看起来是这样的:

public class Book { 
    private int id; 
    private String title; 
    private String author; 

    public Book() {} 

    // NEW 
    public Book(String title, String author) { 
     this.title = title; 
     this.author = author; 
    } 

    // READ 
    public Book(int id, String title, String author) { 
     this.id = id; 
     this.title = title; 
     this.author = author; 
    } 

    // GENERIC ACCESSORS (left out for brevity) 
} 

为了完整 - 工厂:

public class BookFactory extends BaseDatabaseFactory { 

     @Transactional 
    public int createBook(final Book b) { 
     final String insertSQL = "INSERT INTO book (author, title) VALUES (?, ?)"; 
     KeyHolder keyHolder = new GeneratedKeyHolder(); 
     try { 
      int update = jdbcTemplate.update(
        new PreparedStatementCreator() { 
         @Override 
         public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { 
          PreparedStatement ps = connection.prepareStatement(insertSQL, PreparedStatement.RETURN_GENERATED_KEYS); 
          ps.setString(1, b.getTitle()); 
          ps.setString(2, b.getAuthor()); 
          return ps; 
         } 
        }, 
        keyHolder); 
      if (update != 1) { 
       throw new IllegalStateException("Adding book record to database resulted in " + update + " records."); 
      } 
      return keyHolder.getKey().intValue(); 
     } catch (DataAccessException ex) { 
      String msg = "Falied to create new book:" + b.toString() + "ex:" + ex.getMessage(); 
      throw new RuntimeException(msg); 
     } 
    } 

    private static class BookRowMapper implements RowMapper { 
     @Override 
     public Object mapRow(ResultSet rs, int rowNum) throws SQLException { 
      Book b = new Book(); 
      b.setId(rs.getInt("id")); 
      b.setTitle(rs.getString("title")); 
      b.setAuthor(rs.getString("author")); 
      return b; 
     } 
    } 

    @Transactional 
    public Book readBook(int id) { 
     Book b = null; 
     try { 
      String sql = "SELECT * FROM book WHERE id = " + id; 
      b = (Book) jdbcTemplate.queryForObject(sql, new Object[]{}, new BookRowMapper()); 
     } catch (DataAccessException ex) { 
      throw new RuntimeException("Falied to locate book.", ex); 
     } 
     return b; 
    }  

    @Transactional 
    public boolean updateBook(final Book b) { 
     final String updateSQL = "UPDATE book SET author = ?, title = ? WHERE id = ?"; 
     try { 
      jdbcTemplate.update(
        new PreparedStatementCreator() { 
         @Override 
         public PreparedStatement createPreparedStatement(Connection connection) throws SQLException { 
          PreparedStatement ps = connection.prepareStatement(updateSQL); 
          ps.setString(1, b.getAuthor()); 
          ps.setString(2, b.getTitle()); 
          ps.setInt(3, b.getId()); 
          return ps; 
         } 
        }); 
     } catch (DataAccessException ex) { 
      log.error("Falied to update Book:" + b.toString(), ex); 
      return false; 
     } 
     return true; 
    } 

    @Transactional 
    public boolean deleteBook(final Book b) { 
     try { 
      jdbcTemplate.update("DELETE FROM book WHERE id = ?", b.getId()); 
     } catch (DataAccessException ex) { 
      log.error("Falied to delete Book:" + b.toString(), ex); 
      return false; 
     } 
     return true; 
    }  
} 
+0

错误看起来很熟悉,您可以发布Book的代码吗? – Reimeus 2012-07-11 21:30:22

+0

@Reimeus当然,非常喜欢POJO。你想让我发布工厂吗? – Colin 2012-07-11 21:42:36

回答

1

你能做出的ID一个整数类型呢?看起来好像工厂在更新b2时会导致创建新实例。 id应该是可空的。

+0

感谢您的输入,它帮助我找到了问题。 – Colin 2012-07-11 22:10:18

+0

好的,太好了。如果您使用的是ORM(例如Hibernate),则只需要为空即可。 – Reimeus 2012-07-11 22:52:23

+0

您是否知道是否有可以生成普通的旧SQL CRUD代码的应用程序?我发现的生成工具仅适用于JPA和/或Hibernate。 – Colin 2012-07-12 07:55:11

0

修正了在尝试记录问题时(通过粘贴整齐的工厂代码)的问题。 createBook(book)和readBook(int)工厂方法缺少@Transactional注释;我曾认为事务是从调用测试容器继承而来的 - 显然情况并非如此。在事务之间分割测试必须创建该行的多个实例。

我希望这可以帮助遇到同样问题的其他人。

+1

你应该能够在BookFactory的类级别放置@Transactional(propagation = Propagation.REQUIRED),以使所有方法事务化:) – Reimeus 2012-07-11 22:44:44

相关问题