2011-02-10 35 views
20

Tôi có đoạn mã sau:Làm thế nào để tái sử dụng cùng một kết nối với JdbcTemplate của Spring?

 

    @Test 
    public void springTest() throws SQLException{ 
     //Connect to the DB. 
     DriverManagerDataSource dataSource = new DriverManagerDataSource(); 
     dataSource.setDriverClassName("org.h2.Driver"); 
     dataSource.setUrl("jdbc:h2:/data/h2/testa"); 
     dataSource.setUsername(""); 
     dataSource.setPassword(""); 
     JdbcTemplate jt=new JdbcTemplate(dataSource); 
     jt.execute("SELECT 1"); 
     jt.execute("SELECT 1"); 
    } 
 

Tôi hy vọng hai thực hiện() dòng để tái sử dụng cùng một kết nối. Tuy nhiên, dữ liệu ghi nhận nói:

 
2011-02-10 12:24:17 DriverManagerDataSource [INFO] Loaded JDBC driver: org.h2.Driver 
2011-02-10 12:24:17 JdbcTemplate [DEBUG] Executing SQL statement [SELECT 1] 
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Fetching JDBC Connection from DataSource 
2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] Creating new JDBC DriverManager Connection to [jdbc:h2:/data/h2/testa] 
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Returning JDBC Connection to DataSource 
2011-02-10 12:24:17 JdbcTemplate [DEBUG] Executing SQL statement [SELECT 1] 
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Fetching JDBC Connection from DataSource 
2011-02-10 12:24:17 DriverManagerDataSource [DEBUG] Creating new JDBC DriverManager Connection to [jdbc:h2:/data/h2/testa] 
2011-02-10 12:24:17 DataSourceUtils [DEBUG] Returning JDBC Connection to DataSource 

Ví dụ trên chạy khá nhanh nhưng tôi có một mảnh lớn của mã nào về cơ bản là điều tương tự và bị treo trong một thời gian dài trên Creating new JDBC DriverManager Connection. Tôi không bao giờ gặp lỗi nhưng nó làm cho mã chạy rất chậm. Tôi có thể bằng cách nào đó refactor mã trên để chỉ sử dụng cùng một kết nối?

Cảm ơn

Trả lời

16

Đây là một n ví dụ sử dụng Apache DBCP: -

BasicDataSource dbcp = new BasicDataSource(); 
dbcp.setDriverClassName("com.mysql.jdbc.Driver"); 
dbcp.setUrl("jdbc:mysql://localhost/test"); 
dbcp.setUsername(""); 
dbcp.setPassword(""); 

JdbcTemplate jt = new JdbcTemplate(dbcp); 
jt.execute("SELECT 1"); 
jt.execute("SELECT 1"); 

Sản lượng log4j là: -

[DEBUG] [JdbcTemplate] [execute:416] - Executing SQL statement [SELECT 1] 
[DEBUG] [DataSourceUtils] [doGetConnection:110] - Fetching JDBC Connection from DataSource 
[DEBUG] [DataSourceUtils] [doReleaseConnection:332] - Returning JDBC Connection to DataSource 
[DEBUG] [JdbcTemplate] [execute:416] - Executing SQL statement [SELECT 1] 
[DEBUG] [DataSourceUtils] [doGetConnection:110] - Fetching JDBC Connection from DataSource 
[DEBUG] [DataSourceUtils] [doReleaseConnection:332] - Returning JDBC Connection to DataSource 
+0

Đó là một thay đổi dễ dàng và dường như nó đã hoạt động. Nó có an toàn không? – User1

+2

Đó là chủ đề an toàn, bởi vì đây là những gì một hồ bơi kết nối là tất cả về. :) Tôi cũng sử dụng nó trong các trường hợp thử nghiệm của mình, cho phép tôi tạo ra 'dataSource' thay vì lập trình nó. – limc

+2

Wow mã lớn của tôi chỉ nhanh hơn 100 lần! – User1

4

Looking at the Spring's code đây là hiểu biết của tôi ở mức cao.

Bạn đang tạo một DriverManagerDataSource. Việc này sử dụng nội bộ DataSourceUtils để nhận kết nối. Và nó chỉ reuses kết nối nếu có một giao dịch đang hoạt động trong tiến trình. Vì vậy, nếu bạn chạy cả hai thực hiện trong một giao dịch duy nhất thì nó sẽ sử dụng cùng một kết nối. Hoặc bạn cũng có thể sử dụng tổng hợp với 1 kết nối để kết nối được tạo và sử dụng lại.

+1

oops không tìm thấy! – Shams

+1

@shams Liên kết được cập nhật. Và bạn cũng có thể thấy câu trả lời của tôi từ cấp mã nguồn :) – coderz

23

Spring cung cấp một DataSource đặc biệt cho phép bạn làm điều này: SingleConnectionDataSource

Thay đổi mã của bạn để điều này sẽ làm các trick:

SingleConnectionDataSource dataSource = new SingleConnectionDataSource(); 
.... 
// The rest stays as is 

Để sử dụng trong các ứng dụng đa luồng, bạn có thể làm cho lấy lại mã bằng cách mượn một kết nối mới từ hồ bơi và gói nó quanh phần mã cơ sở chuyên sâu:

// ... this code may be invoked in multiple threads simultaneously ... 

try(Connection conn = dao.getDataSource().getConnection()) { 
    JdbcTemplate db = new JdbcTemplate(new SingleConnectionDataSource(conn, true)); 

    // ... database-intensive code goes here ... 
    // ... this code also is safe to run simultaneously in multiple threads ... 
    // ... provided you are not creating new threads inside here 
} 
+2

Điều này không thể được sử dụng vì SingleConnectionDataSource không phải là chủ đề an toàn từ tài liệu java của nó "Rõ ràng, đây không phải là đa luồng có khả năng". Và vì các phương thức thử nghiệm riêng lẻ được thực thi trong chủ đề của riêng chúng bởi junit nên OP phải thiết lập SingleConnectionDataSource trong mỗi phương thức đánh bại perf mà bạn đang tìm kiếm –

+4

Trường hợp sử dụng được mô tả trong câu hỏi không có cách nào để hỗ trợ đa luồng là một yêu cầu. –

+4

Không sử dụng cùng một kết nối trong nhiều chủ đề không an toàn theo chủ đề, theo định nghĩa? –

4

Bạn cần các cuộc gọi được bao bọc trong một giao dịch duy nhất. Thông thường bạn sẽ làm điều này với chú thích AOP + @Transactional của Spring trong một ứng dụng. Bạn cũng có thể làm điều đó theo lập trình với một PlatformTranactionManager, một TransactionTemplate và gói mã để thực thi trong một số TransactionCallback. Xem transaction documentation.

3

Trong một từ, Spring JDBCTemplate DriverManagerDataSource không hỗ trợ nhóm kết nối. Nếu bạn muốn sử dụng hồ bơi kết nối, DBCPC3P0 là cả hai lựa chọn tốt.

Hãy đi qua JDBCTemplate mã nguồn để xem lý do tại sao ...

Không có vấn đề gọi update, queryForObject và các phương pháp khác, cuối cùng họ sẽ gọi execute phương pháp:

@Override 
    public <T> T execute(ConnectionCallback<T> action) throws DataAccessException { 
     Assert.notNull(action, "Callback object must not be null"); 

     Connection con = DataSourceUtils.getConnection(getDataSource()); 
     try { 
      Connection conToUse = con; 
      if (this.nativeJdbcExtractor != null) { 
       // Extract native JDBC Connection, castable to OracleConnection or the like. 
       conToUse = this.nativeJdbcExtractor.getNativeConnection(con); 
      } 
      else { 
       // Create close-suppressing Connection proxy, also preparing returned Statements. 
       conToUse = createConnectionProxy(con); 
      } 
      return action.doInConnection(conToUse); 
     } 
     catch (SQLException ex) { 
      // Release Connection early, to avoid potential connection pool deadlock 
      // in the case when the exception translator hasn't been initialized yet. 
      DataSourceUtils.releaseConnection(con, getDataSource()); 
      con = null; 
      throw getExceptionTranslator().translate("ConnectionCallback", getSql(action), ex); 
     } 
     finally { 
      DataSourceUtils.releaseConnection(con, getDataSource()); 
     } 
    } 

Nó kêu gọi DataSourceUtils.getConnection phương pháp để có được kết nối và DataSourceUtils.releaseConnection để giải phóng kết nối.

Từ DataSourceUtils mã nguồn, chúng tôi thấy Connection con = dataSource.getConnection();con.close();.

Điều đó có nghĩa là hoạt động kết nối được xác định bằng cách triển khai giao diện DataSource và thao tác kết nối chặt chẽ được xác định bằng cách triển khai giao diện Connection. Điều này cho phép các triển khai DataSource/Connection dễ dàng chèn vào Spring JDBCTemplate.

Việc triển khai DataSource trong Spring JDBCTemplate là DriverManagerDataSource. Từ:

protected Connection getConnectionFromDriverManager(String url, Properties props) throws SQLException { 
    return DriverManager.getConnection(url, props); 
} 

public static void doCloseConnection(Connection con, DataSource dataSource) throws SQLException { 
    if (!(dataSource instanceof SmartDataSource) || ((SmartDataSource) dataSource).shouldClose(con)) { 
     con.close(); 
    } 
} 

Chúng tôi nhận thấy mỗi lần nó trả về một kết nối mới, và kết nối hiện chặt chẽ. Đó là lý do tại sao nó không hỗ trợ hồ bơi kết nối.

Khi ở trong DBCP, triển khai DataSourcePoolingDataSource, chúng tôi thấy getConnection() là từ một hồ bơi kết nối; việc triển khai ConnectionPoolableConnection, chúng tôi thấy phương thức close() không được đóng kết nối, thay vào đó nó trả về kết nối với nhóm kết nối.

Đó là sự kỳ diệu!

0

Tôi biết điều này là tình huống (tùy thuộc vào tập hợp tính năng bạn muốn sử dụng), nhưng bạn có thể chỉ cần sử dụng các phương pháp JdbcTemplate.batchUpdate.

+0

Tôi nghĩ điều này rất hữu ích cho Phụ trang và cập nhật , nhưng không chọn trừ khi có một cách thông minh mà bạn có thể thu được kết quả từ việc này ... nhưng trong trường hợp đó, một câu lệnh SQL được xác định rõ hơn có lẽ sẽ hiệu quả hơn. –

Các vấn đề liên quan