복붙노트

[SPRING] JDBC 템플릿 용으로 어떤 트랜잭션 관리자를 사용해야합니까? JPA를 사용할 때?

SPRING

JDBC 템플릿 용으로 어떤 트랜잭션 관리자를 사용해야합니까? JPA를 사용할 때?

JPA 트랜잭션에 표준 JPA 트랜잭션 관리자를 사용하고 있습니다. 그러나 이제 동일한 '데이터 소스'를 공유 할 JDBC 엔티티를 추가하려고합니다. Spring 트랜잭션으로 JDBC 작업을 트랜잭션화할 수 있습니까? JTA 트랜잭션 관리자로 전환해야합니까? 동일한 데이터 소스에서 JPA 및 JDBC 트랜잭션 서비스를 모두 사용할 수 있습니까? 더 좋게도,이 두 가지 거래를 혼합 할 수 있습니까?

UPDATE : @ 에펜 :

나는 데이터베이스 행을 삽입하기 위해 getSimpleJDBCTemplate.update를 사용하는 SimpleJdbcDaoSupport에서 확장 된 DAO를 가지고있다. 서비스 코드에서 RuntimeException이 발생하면 JPATransactionManager를 사용할 때 트랜잭션이 롤백되지 않습니다. DatasourceTransactionManager를 사용할 때 롤백을 수행합니다. JPATransactionManager를 디버깅하려고 시도했는데 기본 JDBCConnection에서 롤백을 수행하지 못하는 것 같습니다 (데이터 소스가 JPA 용 JDBC 일 필요는 없다는 사실로 추측합니다). 제 구성 설정은 여기에서 설명한 것과 정확히 같습니다.

다음은 테스트 코드입니다.

<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>

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;
}

}

다음은 dao를 호출하고 예외 (스프링 서비스)를 호출하는 클라이언트 코드입니다.

@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();
}

참고 :이 코드는 DatasourceTransactionManager를 사용할 때 예상대로 작동합니다.

업데이트 - 2 :

문제의 근본 원인을 찾았습니다. Espen에게 감사드립니다.

엔티티 관리자 구성은 다음과 같습니다 (봄용 애완 동물 클리닉 앱에서 복사).

    <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="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>

이제 모든 것이 작동하는 것 같습니다! 누구나이 두 가지 접근 방식의 차이점을 설명 할 수 있습니까?

해결법

  1. ==============================

    1.JPA와 JDBC 코드를 JpaTransactionManager를 사용하여 같은 트랜잭션에서 혼합 할 수 있습니다.

    JPA와 JDBC 코드를 JpaTransactionManager를 사용하여 같은 트랜잭션에서 혼합 할 수 있습니다.

    Spring 3의 JavaDoc에서 발췌 한 내용 :

    JPA는 쿼리를 캐시하고 트랜잭션이 끝날 때 쿼리를 모두 실행한다는 것을 알고 있어야합니다. 따라서 JPA를 사용하여 트랜잭션 내에서 일부 데이터를 유지 한 다음 JDBC를 사용하여 데이터를 검색하려는 경우 JPA의 지속성 컨텍스트를 명시 적으로 플러시하지 않고 JDBC 코드를 사용하여이를 검색하려고하지 않으면 작동하지 않습니다.

    JPA 코드가 트랜잭션 내부에서 행을 삭제했다는 것을 JDBC 코드로 선언하는 코드 예제 :

    @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);
    }
    

    JpaTemplate 클래스를 사용하려면 entityManager.flush ()를 jpaTemplate.flush ()로 바꿉니다.

    Sajids의 의견에 대한 응답으로 : Spring을 사용하면 다음과 같이 JPA와 JDBC를 모두 지원하는 트랜잭션 관리자를 구성 할 수 있습니다.

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

    주석 기반 버전

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

    JDBC 쿼리를 작동 시키려면 JdbcTemplate 또는 SimpleJdbcTemplate 클래스를 사용하여 실행해야합니다. SimpleJdbcDaoSupport를 확장하는 DAO의 경우, getSimpleJdbcTemplate (..) 메소드를 사용해야한다.

    그리고 마지막으로 두 개의 DAO 메소드가 같은 트랜잭션에 참여하게하려면 @Transactional 주석이 붙은 서비스 클래스 메소에서 두 DAO 메소드를 모두 호출하십시오. 설정에서 요소를 사용하면 Spring은 주어진 트랜잭션 관리자로 트랜잭션을 처리 할 것이다.

    비즈니스 계층에서 :

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

    편집 2 : 그러면 뭔가 잘못입니다. Javadoc에 지정된대로 정확히 작동합니다. 귀하의 엔티티 관리자는 아래의 bean과 같은 데이터 소스 속성을 가지고 있습니까? 엔티티 관리자와 확장 된 JpaDaoSupport 클래스에 동일한 데이터 소스를 주입하는 동안에 만 작동합니다.

    <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>
    
  2. ==============================

    2.JDBC와 JPA를 섞어 보지 않았지만 아직 XA 데이터 소스에 대한 JDBC 연결을 얻은 경우에는 JTA 트랜잭션입니다. 따라서 트랜잭션을 켠 상태에서 Stateless 세션 빈에서 코드를 실행하면 JTA가 관리하는 Entities와 JDBC를 자동으로 가져옵니다.

    JDBC와 JPA를 섞어 보지 않았지만 아직 XA 데이터 소스에 대한 JDBC 연결을 얻은 경우에는 JTA 트랜잭션입니다. 따라서 트랜잭션을 켠 상태에서 Stateless 세션 빈에서 코드를 실행하면 JTA가 관리하는 Entities와 JDBC를 자동으로 가져옵니다.

    편집하다 다음은 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();
    }
    

    면책 조항 : 코드가 테스트되지 않았습니다.

    이것이 봄이 아니라는 것을 깨달으십시오. 그러나 어쨌든 그것을 떠날 것입니다.

  3. from https://stackoverflow.com/questions/2673678/what-transaction-manager-should-i-use-for-jbdc-template-when-using-jpa by cc-by-sa and MIT license