2010-04-20 30 views
22

Tôi đang sử dụng trình quản lý giao dịch JPA tiêu chuẩn cho các giao dịch JPA của mình. Tuy nhiên, bây giờ tôi muốn thêm một số thực thể JDBC sẽ chia sẻ cùng một 'nguồn dữ liệu'. Làm thế nào tôi có thể thực hiện các hoạt động giao dịch JDBC với giao dịch mùa xuân? Tôi có cần chuyển sang người quản lý giao dịch JTA không? Có thể sử dụng cả dịch vụ giao dịch JDBC JPA & JDBC với cùng một nguồn dữ liệu không? Thậm chí tốt hơn, có thể trộn hai giao dịch này không?Tôi nên sử dụng trình quản lý giao dịch nào cho mẫu JBDC Khi sử dụng JPA?

UPDATE: @Espen:

Tôi có một dao kéo dài từ SimpleJdbcDaoSupport trong đó sử dụng getSimpleJDBCTemplate.update để chèn một hàng cơ sở dữ liệu. Khi một RuntimeException được ném từ mã dịch vụ, giao dịch không bao giờ quay trở lại khi sử dụng JPATransactionManager. Nó thực hiện rollback khi sử dụng DatasourceTransactionManager. Tôi đã cố gắng để gỡ lỗi các JPATransactionManager và có vẻ như nó không bao giờ thực hiện rollback trên JDBCConnection cơ bản (tôi đoán do thực tế là các nguồn dữ liệu không nhất thiết phải là JDBC cho JPA). Cài đặt cấu hình của tôi chính xác như bạn đã giải thích ở đây.

Dưới đây là mã thử nghiệm của tôi:

<context:property-placeholder location="classpath:*.properties"/> 

<!-- JPA EntityManagerFactory --> 
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource"/> 
    <property name="persistenceXmlLocation" 
     value="classpath:/persistence-test.xml" /> 
    <property name="persistenceProvider"> 
     <bean class="org.hibernate.ejb.HibernatePersistence" /> 
    </property> 

</bean> 

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
    <property name="entityManagerFactory" ref="entityManagerFactory"/> 
</bean> 

<!-- 
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 
--> 

<!-- Database connection pool --> 
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
    <property name="driverClassName" value="${database.driverClassName}" /> 
    <property name="url" value="${database.url}" /> 
    <property name="username" value="${database.username}" /> 
    <property name="password" value="${database.password}" /> 
    <property name="testOnBorrow" value="${database.testOnBorrow}" /> 
    <property name="validationQuery" value="${database.validationQuery}" /> 
    <property name="minIdle" value="${database.minIdle}" /> 
    <property name="maxIdle" value="${database.maxIdle}" /> 
    <property name="maxActive" value="${database.maxActive}" /> 
</bean> 




<!-- Initialize the database --> 
<!--<bean id="databaseInitializer" class="com.vantage.userGroupManagement.logic.StoreDatabaseLoader"> 
    <property name="dataSource" ref="storeDataSource"/> 
</bean>--> 

<!-- ANNOTATION SUPPORT --> 

<!-- Enable the configuration of transactional behavior based on annotations --> 
<tx:annotation-driven transaction-manager="transactionManager"/> 

<!-- JPA annotations bean post processor --> 
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/> 

<!-- Exception translation bean post processor (based on Repository annotation) --> 
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/> 

<!-- throws exception if a required property has not been set --> 
<bean class="org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor"/> 


<bean id="userService" class="com.rfc.example.service.UserServiceImpl"> 
    <property name="userDao" ref="userDao"></property> 
    <property name="contactDao" ref="contactDao"></property> 
    <property name="callRecordingScheduledProgramTriggerDAO" ref="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAO"></property> 
</bean> 

<bean id="userDao" class="com.rfc.example.dao.UserDaoJPAImpl" /> 

<bean id="contactDao" class="com.rfc.example.dao.ContactDaoJPAImpl"></bean> 

<bean id="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAO" class="com.rfc.example.dao.CallRecordingScheduledProgramTriggerDAOJDBCImpl"> 
    <property name="dataSource" ref="dataSource"></property> 
</bean> 

Và đây là DAO:

@Transactional 
public class CallRecordingScheduledProgramTriggerDAOJDBCImpl extends SimpleJdbcDaoSupport implements CallRecordingScheduledProgramTriggerDAO{ 
    private static final Log log = LogFactory.getLog(CallRecordingScheduledProgramTriggerDAOJDBCImpl.class); 

@SuppressWarnings("unchecked") 
public CallRecordingScheduledProgramTrigger save(
     CallRecordingScheduledProgramTrigger entity) { 
    log.debug("save -> entity: " + entity); 



    String sql = null; 
    Map args = new HashMap(); 

    String agentIdsString = getAgentIdsString(entity.getAgentIds()); 


    String insertSQL = "insert into call_recording_scheduled_program_trigger" + 
      "  ( queue_id, queue_id_string, agent_ids_string, caller_names, caller_numbers, trigger_id, note, callcenter_id, creator_id_string, creator_id) " + 
      " values(:queueId, :queueIdString, :agentIdsString, :callerNames, :callerNumbers, :triggerId, :note, :callcenterId , :creatorIdString, :creatorId )"; 

    args.put("queueId", entity.getQueueId()); 
    args.put("agentIdsString",agentIdsString); 
    args.put("callerNames", entity.getCallerNames());  
    args.put("queueIdString", entity.getQueueIdString()); 
    args.put("callerNumbers", entity.getCallerNumbers()); 
    args.put("triggerId", entity.getTriggerId()); 
    args.put("note", entity.getNote()); 
    args.put("callcenterId", entity.getCallcenterId()); 
    args.put("creatorId", entity.getCreatorId()); 
    args.put("creatorIdString", entity.getCreatorIdString()); 

    sql = insertSQL; 
    getSimpleJdbcTemplate().update(sql, args); 
    System.out.println("saved: ----------" + entity); 
    return entity; 
} 

} 

Đây là mã khách hàng mà các cuộc gọi dao và ném ngoại lệ (dịch vụ xuân)

@Transactional(propagation=Propagation.REQUIRED) 
public void jdbcTransactionTest() { 
    System.out.println("entity: "); 
    CallRecordingScheduledProgramTrigger entity = new CallRecordingScheduledProgramTrigger(); 

    entity.setCallcenterId(10L); 
    entity.setCreatorId(22L); 
    entity.setCreatorIdString("sajid"); 
    entity.setNote(System.currentTimeMillis() + ""); 
    entity.setQueueId(22); 
    entity.setQueueIdString("dddd"); 
    String triggerId = "id: " + System.currentTimeMillis(); 
    entity.setTriggerId(triggerId); 
    callRecordingScheduledProgramTriggerDAO.save(entity); 

    System.out.println("entity saved with id: " + triggerId); 

    throw new RuntimeException(); 
} 

Chú ý: mã hoạt động như mong đợi khi sử dụng DatasourceTransactionManager

CẬP NHẬT - 2:

Ok Tôi đã tìm thấy nguyên nhân gốc rễ của vấn đề. Cảm ơn Espen.

cấu hình quản lý thực thể của tôi là như thế này (sao chép từ mùa xuân ứng dụng thú cưng-phòng khám):

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource"/> 
    <property name="persistenceXmlLocation" 
     value="classpath:/persistence-test.xml" /> 
    <property name="persistenceProvider"> 
     <bean class="org.hibernate.ejb.HibernatePersistence" /> 
    </property> 

</bean> 

Sau đó, tôi đã thay đổi nó như thế này:

<bean id="entityManagerFactory" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="persistenceXmlLocation" 
     value="classpath:/persistence-test.xml" /> 
    <property name="dataSource" ref="dataSource"/> 

    <property name="jpaVendorAdapter"> 
     <bean 
      class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
     <property name="showSql" value="true" /> 
     <property name="generateDdl" value="true" /> 
     <property name="databasePlatform" value="org.hibernate.dialect.MySQL5Dialect" /> 
    </bean> 

</property> 
</bean> 

Bây giờ tất cả mọi thứ dường như được làm việc! Bất cứ ai có thể giải thích sự khác biệt giữa hai cách tiếp cận này?

+0

lẽ liên quan http://stackoverflow.com/questions/2650409/implement-custom-jta-xaresource-for-using-with-hibernate/2651580#2651580 – ewernli

+0

Hãy cố gắng loại bỏ các tài sản persistenceXmlLocation. Đó là một thay thế cho thuộc tính dataSource. Các yêu cầu cho JpaTransactionManager hoạt động với cả hai truy vấn JPA và JDBC là thực thể của bạnManager sử dụng cùng một nguồn dữ liệu như các truy vấn JDBC và bạn chỉ định phương ngữ JPA như bạn đã làm. – Espen

Trả lời

25

Có thể trộn JPA và JDBC mã trong cùng một giao dịch bằng cách sử dụng JpaTransactionManager.

Một đoạn từ mùa xuân 3 của JavaDoc:

quản lý giao dịch này cũng hỗ trợ truy cập DataSource trực tiếp trong một giao dịch (ví dụ: đồng bằng mã JDBC làm việc với DataSource giống nhau). Điều này cho phép các dịch vụ trộn truy cập JPA và các dịch vụ sử dụng JDBC đơn giản (mà không cần biết JPA)!

Bạn nên lưu ý rằng JPA lưu trữ các truy vấn và thực hiện tất cả chúng vào cuối giao dịch. Vì vậy, nếu bạn muốn tồn tại một số dữ liệu bên trong một giao dịch với JPA và sau đó lấy dữ liệu bằng JDBC, nó sẽ không hoạt động mà không xóa hoàn toàn ngữ cảnh kiên trì của JPA trước khi bạn cố gắng truy xuất nó bằng mã JDBC.

Một ví dụ mã mà khẳng định với mã JDBC rằng mã JPA đã xóa một hàng bên trong một giao dịch:

@Test 
@Transactional 
@Rollback(false) 
public void testDeleteCoffeeType() { 

    CoffeeType coffeeType = coffeeTypeDao.findCoffeeType(4L); 
    final String caffeForte = coffeeType.getName(); 

    coffeeTypeDao.deleteCoffeeType(coffeeType); 
    entityManager.flush(); 

    int rowsFoundWithCaffeForte = jdbcTemplate 
     .queryForInt("SELECT COUNT(*) FROM COFFEE_TYPES where NAME = ?", 
      caffeForte); 
    assertEquals(0, rowsFoundWithCaffeForte); 
} 

Và nếu bạn muốn sử dụng lớp JpaTemplate, chỉ cần thay thế các entityManager.flush() với jpaTemplate.flush();

Để trả lời nhận xét của Sajids: Với Spring, bạn có thể định cấu hình trình quản lý giao dịch hỗ trợ cả JPA và JDBC như sau:

<tx:annotation-driven transaction-manager="transactionManager" /> 

<!-- Transaction manager --> 
<bean id="transactionManager" class="org.springframework.orm.jpa 
      .JpaTransactionManager"> 
    <property name="entityManagerFactory" ref="entityManagerFactory" /> 
</bean> 

và Annotation-Driven phiên bản

@Bean 
public JpaTransactionManager transactionManager(EntityManagerFactory emf) { 
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager(); 
    jpaTransactionManager.setEntityManagerFactory(emf); 
    return jpaTransactionManager; 
} 

Để làm cho nó hoạt động, các truy vấn JDBC phải được thực hiện với các JdbcTemplate hoặc lớp SimpleJdbcTemplate. Trong trường hợp của bạn với DAO mở rộng SimpleJdbcDaoSupport, bạn nên sử dụng phương thức getSimpleJdbcTemplate (..).

Và cuối cùng để cho hai phương thức DAO tham gia vào cùng một giao dịch, hãy gọi cả hai phương thức DAO từ một lớp dịch vụ được chú thích với @Transactional. Với phần tử <tx:annotation-driven> trong cấu hình của bạn, Spring sẽ xử lý giao dịch cho bạn với trình quản lý giao dịch đã cho.

Trên lớp kinh doanh:

public class ServiceClass {.. 

@Transactional 
public void updateDatabase(..) { 
    jpaDao.remove(..); 
    jdbcDao.insert(..); 
} 
} 

Chỉnh sửa 2: Sau đó, cái gì là sai. Nó làm việc cho tôi chính xác như được chỉ định trong Javadoc. Người quản lý thực thể của bạn có thuộc tính nguồn dữ liệu như bean của tôi bên dưới không? Nó sẽ chỉ hoạt động khi bạn đang tiêm cùng một nguồn dữ liệu vào trình quản lý thực thể và các lớp JpaDaoSupport mở rộng của bạn.

<bean id="entityManagerFactoryWithExternalDataSoure" primary="true" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource" /> 
    <property name="jpaVendorAdapter"> 
     <bean class="org.springframework.orm.jpa.vendor 
       .HibernateJpaVendorAdapter" /> 
    </property> 
    <property name="jpaProperties"> 
     <value> 
      hibernate.format_sql=true 
     </value> 
    </property> 
</bean> 
+0

@Espen Thaks cho câu trả lời. Tuy nhiên, tôi quan tâm nhiều hơn đến việc triển khai thực hiện JDBC Dao độc lập. Ví dụ, tôi có UserDaoJPAImpl cho người dùng và OrderDaoJDBCImpl cho đơn đặt hàng. OrderDaoJDBCImpl mở rộng 'SimpleJdbcDaoSupport'. vấn đề đầu tiên của tôi, Làm cách nào để tôi thực hiện giao dịch OrderDaoJDBCImpl (khai báo hoặc chú thích hoặc xml). Tôi có phải viết một người quản lý giao dịch khác không? vấn đề thứ hai của tôi. Làm cách nào để kết hợp hai giao dịch này? Tôi có thể thực hiện điều này trong giao dịch EJB 2.1 CMT và BMT (sử dụng JTA). Nhưng với Spring & JPA, có thể không có JTA không? – Sajid

+0

@Sajid: Các giao dịch khai báo hoạt động gần như giống với Spring như với EJB 3. Thay vì @TransactionAttribute bạn sử dụng @Transactional thay thế. Và có, nó chắc chắn có thể không có JTA và với mùa xuân. Nó chỉ yêu cầu tất cả các truy vấn được thực thi dựa trên cùng một nguồn dữ liệu. A – Espen

+0

Tôi có một dao mở rộng từ SimpleJdbcDaoSupport sử dụng getSimpleJDBCTemplate.update để chèn một hàng cơ sở dữ liệu. Khi một RuntimeException được ném từ mã dịch vụ, giao dịch không bao giờ quay trở lại khi sử dụng JPATransactionManager. Nó thực hiện rollback khi sử dụng DatasourceTransactionManager. Tôi đã cố gắng gỡ lỗi JPATransactionManager và dường như nó không bao giờ thực hiện khôi phục trên JDBCConnection cơ bản (tôi đoán do thực tế là nguồn dữ liệu không nhất thiết phải là JDBC cho JPA). Cài đặt cấu hình của tôi chính xác như bạn đã giải thích ở đây. – Sajid

0

Tôi chưa thực sự làm việc này chi tiết như tôi đã không trộn lẫn cả JDBC lẫn JPA nhưng nếu bạn nhận được kết nối JDBC của bạn cho một nguồn dữ liệu XA thì đó là giao dịch JTA. Vì vậy, nếu bạn chạy mã của bạn trong bean phiên Stateless ví dụ với giao dịch được bật, thì bạn sẽ tự động nhận được cả các thực thể và JDBC do JTA quản lý.

EDIT Dưới đây là một ví dụ mã từ Servlet

private @Resource DataSource xaDatasource; 
private @Resource UserTransaction utx; 
private @PersistenceUnit EntityManagerFactory factory; 

public void doGet(HttpServletRequest req, HttpServletResponse res) ... { 
    utx.begin(); 
    //Everything below this will be in JTA 
    Connection conn = xaDatasource.getConnection(); 
    EntityManager mgr = factory.createEntityManager(); 
    //Do your stuff 
    ... 
    utx.commit(); 
} 

Disclaimer: Mã không được kiểm tra.

Chỉ cần nhận ra điều này không phải là mùa xuân nhưng tôi sẽ để nó lên anyway

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