Spring은 자원관리측면에서 Hibernate, JDO, Oracle TopLink, Apache OJB, iBATIS SQL Map 그리고 JPA : DAO구현 지원, 트랜잭션 전략등의 통합을 제공한다. Hibernate를 예로 들어볼때, 많은 Hibernate통합 이슈를 할당하는 편리한 IoC 특성을 가진 가장 중요한 클래스 지원이 있다. O/R매퍼을 위한 이러한 모든 지원 패키지는 Spring의 일반적 트랜잭션과 DAO예외 구조의 요구에 응한다. 여기엔 두가지 통합 스타일(Spring의 DAO 'template' 이나 명백한 Hibernate/JDO/TopLink/등등 API에 대한 DAO를 코딩하는)이 있다. 이러한 두 경우, DAO는 의존성삽입을 통해 설정되고 Spring의 자원및 트랜잭션 관리에 참여할수 있다.
Spring은 데이터접근 애플리케이션을 생성하기 위해 당신이 선택한 O/R mapping레이어를 사용할때 중요한 지원을 추가한다. 그중에 첫번째는 당신은 O/R맵핑을 위해서 Spring에서 제공하는것을 사용해서 시작해야 한다는것을 알아야만 한다. 당신은 모든것을 해야 할 필요는 없다. 확장하는것과는 상관없이 당신은 다시보기 위해서 초대되었고 Spring접근에 영향을 끼친다. 유사한 하부조직을 만드는 노력과 위험을 가지는데 대해 결정을 먼저해야 한다. 대부분의 O/R맵핑지원은 라이브러리 스타일내에서 사용되는 기술과는 상관없이 모든것은 재사용가능한 자바빈처럼 디자인 되었다. Spring IoC 컨테이너내부에서의 사용은 설정과 배치의 쉬움이라는 면에서 추가적인 이득을 제공한다. 이 섹션의 예제들은 Spring Applicationcontext내에서 설정이 됨을 보여준다.
다음은 O/R맵핑 DAO를 생성하기 위해 Spring을 사용함으로써 발생하는 몇몇 장점들이다.
테스트의 용이함(ease) Spring의 IoC접근은 Hibernate SessionFactory인스턴스의 구현과 설정 위치, JDBC DataSource, 트랜잭션 관리자 그리고 매퍼 객체 구현물을 쉽게 교체할수 있게 만든다. 이것은 격리수준내에서 각각의 영속성 관련 코드를 쉽게 격리하고 테스트 할수 있게 만든다.
공통적인 데이터 접근에 관련된 예외 Spring은 자체적인 예외(잠재적으로는 체크된)를 공통적인 런타임 DataAccessException구조로 변환하도록 당신이 선택한 O/R맵핑툴로부터 예외를 포장할수 있다. 이것은 당신에게 적절한 레이어에서 짜증나는 반복적인 catches/throws와 예외 선언없이 회복할수 없는 대부분의 영속성 예외를 다루도록 해준다. 당신은 필요한 어느 지점에서 예외를 추출하고 다룰수 있다. JDBC예외는 일관적인 프로그래밍 모델내에서 JDBC로 몇가지 작업을 수행할수 있다는 것을 의미하면서 같은 구조로 변환되는 것을 기억하라.
일반적인 자원 관리 Spring애플리케이션 컨텍스트는 Hibernate SessionFactory 인스턴스, JDBC Datasource, iBATIS SQLMaps설정 객체, 그리고 다른 관련 자원의 위치와 설정을 다룰수 있다. 이것은 그런 값들을 쉽게 관리하고 변경할수 있게 만든다. Spring은 효율적이고 쉽고 안전하게 영속성 자원을 다룰수 있는 기능을 제공한다. 예를 들어, Hibernate를 사용하는 관련코드는 효율적이고 적합한 트랜잭션 관리를 위해 Hibernate Session객체를 사용할 필요가 있다. Spring은 Java코드 레벨에서 'template' 래퍼 클래스를 명시적으로 사용하거나 Hibernate SessionFactory(명백한 Hibernate3 API에 기반한 DAO를 위한)를 통해 현재 Session을 나타내서 현재 쓰레드에 Session을 투명하게 생성하고 연결하는(bind) 것을 쉽게 해준다. 게다가 Spring은 어떤 트랜잭션 환경(local 또는 JTA)을 위해 전형적인 Hibernate사용으로부터 반복적으로 발생하는 많은 이슈를 해결한다.
통합된 트랜잭션 관리 Spring은 선언, AOP스타일의 메소드 인터셉터, 또는 자바코드 단계에서 명백한 'template'래퍼 클래스를 가지고 O/R맵핑 코드를 만들수 있다. 이런 경우에 트랜잭션의미는 당신을 위해서 다루어지고 exception이 관리되는 경우에 명백하게 트랜잭션이 다루어진다. 밑에 논의되는 것처럼 당신은 Hibernate/JDO관련 코드에 영향이 없이 다양한 트랜잭션 관리자를 사용하거나 교체할수 있게 되는 장점또한 가지게 된다. 예를 들어, local트랜잭션과 JTA사이에서, 같은 서비스(선언적인 트랜잭션같은)는 두개의 시나리오에서 모두 사용가능하다. 그리고 추가되는 장점은 JDBC관련 코드가 O/R맵핑을 사용하는 코드와 완벽하게 트랜잭션적으로 통합이 될수 있다. 이것은 예를 들면 Hibernate나 iBATIS내에서 구현되지 않은 기능을 다룰 경우에 유용하다. O/R맵핑 작업으로 공통적인 트랜잭션을 공유하는 것이 여전히 필요한 배치처리나 BLOB의 스트리밍처럼 O/R맵핑을 위해 적합하지 않은 데이터 접근에 유용하다.
업체에 종속적인 락(lock-in)방식을 피할수 있고 mix-and-match구현방식을 허용한다. Hibernate는 강력하고 확장이 용이하며 오픈소스이고 free하다. 이것은 여전히 자신만의 API를 사용한다. 더군다나 누구는 iBATIS가 좀더 가볍다는 것에 대해서 논쟁을 벌일수도 있다. 복잡한 O/R맵핑 전략을 요구하지 않는 애플리케이션내에서의 사용은 매우 환상적이다. 주어진 선택에서 이것은 기능과 성능 그리고 다른 어떠한 이유로 다른 구현으로 교체해야 할 경우에 언제나 표준적이고 추상적인 API들을 사용해서 주요한 애플리케이션 기능을 구현하도록 바랄것이다. 예를 들면 Spring의 Hibernate Transaction과 Exception의 추상화는 데이터접근 기능을 구현하고 있는 맵퍼/DAO객체내에서 쉽게 교환할수 있도록 하는 IoC접근으로 Hibernate의 어떠한 성능적 손실없이 당신의 애플리케이션내에서 모든 Hibernate관련 코드를 쉽게 분리하도록 한다. DAO와 함께 처리되는 높은 단계의 서비스 코드는 그 구현에 대해서 어떤것도 알필요가 없다. 이 접근은 mix-and-match접근으로 방해가 되지 않은 방식내에서 의도적으로 데이터접근을 쉽게 구현하도록 만든다는 추가적인 이익을 가져다 준다. 잠재적으로 기존코드를 계속적으로 사용하게 하는것과 각각의 기술의 강력함을 그대로 유지시킨다는 큰 이익을 제공하기도 한다.
Spring배포 패키지내 PetClinic샘플은 DAO를 대체하는 구현물과 JDBC, Hibernate, Oracle TopLink, 그리고 JPA를 위한 애플리케이션 컨텍스트 설정을 제공한다. PetClinic는 Spring웹 애플리케이션내 Hibernate, TopLink 그리고 JPA를 사용하는 샘플 애플리케이션처럼 제공된다. 이것은 다른 트랜잭션 전략을 가진 선언적인 트랜잭션 구분에 영향을 끼친다.
JPetStore 샘플은 Spring환경내 iBATIS SQL Maps의 사용을 보여준다. 이것은 또한 두가지 웹 티어 버전의 특징을 가진다. 하나는 Spring MVC이고 다른 하나는 Struts에 기초를 둔다.
Spring에 포함된 샘플을 넘어서, 여기엔 특정업체가 제공하는 다양한 Spring기반 O/R맵핑이 있다. 예를 들어, JDO구현물인 JPOX(http://www.jpox.org/) and Kodo (http://www.bea.com/kodo).
우리는 Spring이 O/R매퍼를 통합하는 방법을 보여주기 위해 Spring환경에서의 Hibernate(http://www.hibernate.org)에 대해 다루기 시작할것이다. 이 섹션은 많은 이슈를 상세하게 다루고 다양한 DAO구현물과 트랜잭션 구분의 차이점을 보여줄것이다. 대부분의 패턴은 다른 지원되는 O/R맵핑툴로 직접 전환될수 있다. 이 장의 다음 섹션은 다른 O/R매퍼를 다루고, 여기서 예제를 보여준다.
다음의 언급은 Hibernate 3에 집중한다. 이것은 Hibernate의 주요 제품버전이다. Hibernate 2.x는 지원된 이후 Spring에서 계속적으로 지원되고 있다. 다음의 예제는 Hibernate 3클래스와 설정을 모두 사용한다. 이것들 모두는 유사한 Hibernate 2.x지원 패키지인 org.springframework.orm.hibernate를 사용하여 Hibernate 2.x에서 적용될수 있다. Hibernate 3은 org.springframework.orm.hibernate3를 사용한다. org.hibernate패키지에 대한 참조는 Hibernate 3에서의 가장 상위 패키지의 변경에 따라 net.sf.hibernate를 대체할 필요가 있다. 간단히 패키지명(예제에서 사용된)을 따르라.
전형적인 비지니스 애플리케이션은 종종 반복적인 자원 관리 코드가 소스를 뒤죽박죽 만든다. 많은 프로젝트를 이런일을 위해서 자신만의 솔루션을 만들기를 시도한다. 때때로 프로그래밍의 편의성을 위한 명백한 실패의 제어를 희생하기도 한다. Spring은 template을 통한 IoC 즉 callback인터페이스와 함께 하부구조 클래스, 또는 AOP인터셉터 적용등으로 명백한 자원 관리를 위한 간단한 해결법을 제공한다. 하부구조는 명백한 자원 핸들링을 하고 체크되지 않은 하부구조 exception구조를 특정 API exception의 적합하게 변환한다. Spring은 어떠한 데이터 접근 전략에도 적용가능한 DAO exception구조를 소개한다. JDBC를 사용하기 위해서는 JdbcTemplate클래스가 connection핸들링을 위해 이전 섹션에서 언급되었고 SQLException이 데이터베이스에 종속적인 SQL에러코드를 의미있는 exception클래스로 해석하는것을 포함하는 DataAccessException구조로 변환된다. 이것은 각각의 Spring트랜잭션 관리자를 통해서 JTA와 JDBC트랜잭션을 지원한다.
Spring은 JdbcTemplate와 유사한 HibernateTemplate/JdoTemplate, HibernateInterceptor/JdoInterceptor 그리고 Hibernate/JDO트랜잭션 관리자로 구성된 Hibernate와 JDO지원을 제공한다. 이것의 커다란 목표는 어떠한 데이터 접근와 트랜잭션 기술을 가지고 깔끔한 애플리케이션 계층화가 애플리케이션 객체의 느슨한 커플링된 상태에서 가능하도록 하는것이다. 데이터 접근과 트랜잭션 전략에서 더이상 비지니스 서비스의 의존성 문제가 없고 더 이상 하드코딩형 자원탐색이 없으며, 더 이상 hard-to-replace싱글톤과 고객 서비스 등록자가 없는것이다. 애플리케이션 객체를 묶는 간단하고 일관적인 접근은 가능한 한 컨테이너 의존성으로 부터 재사용가능하고 free하게 유지시켜준다. 모든 개별적인 데이터 접근 기능은 그들 자신에게는 재사용가능하지만 Spring을 알 필요가 없는 XML기반의 설정과 상호간에 참조되는 자바빈 인스턴스를 제공하는 Spring의 애플리케이션 컨텍스트 개념과 함께 잘 통합된다. 전형적인 Spring애플리케이션에서 많은 중요한 객체(데이터 접근 탬플릿, 탬플릿을 사용하는 데이터 접근 객체, 트랜잭션 관리자, 데이터 접근객체와 트랜잭션 관리자를 사용하는 비지니스 서비스, 웹의 화면 해설자(resolvers), 비지니스 서비스를 사용하는 웹 컨트롤러 등등)는 자바빈이다.
하드코딩형 자원 탐색을 위한 애플리케이션 생성을 피하기 위해서 Spring은 애플리케이션 컨텍스트내에 빈즈처럼 JDBC DataSource나 Hibernate SessionFactory처럼 자원 정의를 하게 한다. 애플리케이션 객체는 빈참조를 통해 미리 선언된 인스턴스에 참조를 받은 자원에 접근하기 위해 필요한 것이다. 다음의 XML애플리케이션 컨텍스트 선언으로 부터 발췌는 JDBC DataSource와 Hibernate SessionFactory를 설정하는 방법을 보여준다.
<beans> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="org.hsqldb.jdbcDriver"/> <property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/> <property name="username" value="sa"/> <property name="password" value=""/> </bean> <bean id="mySessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource"/> <property name="mappingResources"> <list> <value>product.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLDialect </value> </property> </bean> ... </beans>
local Jakarta Commons DBCP BasicDataSource를 JNDI를 사용하는 DataSource로 전환하는 것은 설정상의 문제라는 것을 기억하라.
<beans> <bean id="myDataSource" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/myds"/> </bean> ... </beans>
당신은 이것을 가져오고 나타내기 위한 Spring의 JndiObjectFactoryBean를 사용하여 JNDI를 사용하는 SessionFactory를 에 접근할수도 있다. 어쨌든, EJB컨텍스트외부에서는 대개 필요한 것이 아니다.
탬플릿팅을 위한 기본적인 프로그래밍 모델은 어떤 데이터접근 객체나 비지니스 서비스의 부분이 될수 있는 메소드를 위해 다음처럼 볼수 있다. 펼쳐진 모든 객체의 구현에서 제한은 없다. 이것은 Hibernate의 SessionFactory을 제공할 필요가 있다. 이것은 어디서든 나중에 얻을수 있지만 간단한 setSessionFactory 빈 속성 setter을 통해 Spring애플리케이션 컨텍스트로 부터 빈처럼 참조할것이다. 다음의 작은 조각(snippets)은 Spring애플리케이션 컨텍스트내에서 DAO선언을 보여준다. 위에서 선언된 SessionFactory를 참조하고 있고 DAO메소드 구현을 위한 에제이다.
<beans> ... <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans>
public class ProductDaoImpl implements ProductDao { private SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } public Collection loadProductsByCategory(final String category) throws DataAccessException { HibernateTemplate ht = new HibernateTemplate(this.sessionFactory); return (Collection) ht.execute(new HibernateCallback() { public Object doInHibernate(Session session) throws HibernateException { Query query = session.createQuery( "from test.Product product where product.category=?"); query.setString(0, category); return query.list(); } }); } }
callback구현은 어떤 Hibernate데이터 접근을 위해 사용되는데 영향을 끼칠수 있다. HibernateTemplate은 Session들이 명백하게 열고 닫고 트랜잭션내에서 자동적으로 함께하는것을 확실시한다. 이 탬플릿 인스턴스는 쓰레드에 안전하고(thread-safe) 재사용가능하다. 그들은 주위 클래스의 인스턴스 변수처럼 유지될수 있다. 하나의 검색, 로드, saveOrUpdate또는 삭제 호출처럼 간단한 한단계의 작업은 HibernateTemplate이 대안적으로 편리한 한라인 callback구현처럼 대체될수 있는 메소드를 제공한다. 게다가 Spring은 SessionFactory를 받기위한 setSessionFactory메소드를 제공하는 편리한 HibernateDaoSupport base클래스를 제공한다. 그리고 하위클래스에 의해 사용되기 위한 getSessionFactory과 getHibernateTemplate를 제공한다. 이것은 전형적인 요구사항을 위해 매우 간단한 DAO구현을 허락한다.
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { return getHibernateTemplate().find( "from test.Product product where product.category=?", category); } }
DAO를 구현하기 위해 Spring의 HibernateTemplate을 사용하는 것에 대한 대안처럼, Spring의 일반적인 DataAccessException 구조를 따르는 동안 데이터 접근 코드는 콜백내 Hibernate접근 코드를 포장하지 않는 좀더 전통적인 방법으로 작성될수 있다. Spring의 HibernateDaoSupport base클래스는 현재의 트랜잭션 성질을 가지는 Session에 접근하고 시나리오내 예외로 변환하기 위한 메소드를 제공한다. 유사한 메소드는 SessionFactoryUtils 클래스의 정적 헬퍼처럼 사용가능하다. 이러한 코드는 트랜잭션내에서 강제로 수행하기 위해(생명주기가 트랜잭션에 의해 관리되는 것처럼, 반환된 Session을 닫을 필요성을 제거하는) getSession의 "allowCreate" 플래그를 언제나 "false"로 전달할것이다.
public class ProductDaoImpl extends HibernateDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException, MyException { Session session = getSession(getSessionFactory(), false); try { List result = session.find( "from test.Product product where product.category=?", category, Hibernate.STRING); if (result == null) { throw new MyException("invalid search result"); } return result; } catch (HibernateException ex) { throw convertHibernateAccessException(ex); } } }
이러한 직접적인 Hibernate 접근코드의 가장 큰 장점은 HibernateTemplate가 콜백내에서 체크되지 않은 예외를 제한하는 동안 데이터 접근 코드내에서 던져지는 체크된 애플리케이션 예외를 허용하는 것이다. HibernateTemplate와 작동하는 동안 관련 체크와 콜백후 애플리케이션 예외를 던지는 것을 종종 미룬다. 대개 HibernateTemplate의 편리한 메소드는 더욱 간단하고 많은 시나리오를 위해 좀더 편리하다.
Hibernate 3.0.1은 Hibernate자체가 하나의 Session을 트랜잭션마다 관리하는 "컨텍스트상의 Session(contextual Sessions)"이라고 불리는 기능을 소개했다. 이것은 트랜잭션마다 하나의 Hibernate Session에 대한 Spring의 동기화와 극히 일치한다. 관련 DAO구현물은 명백한 Hibernate API에 기초를 두고 다음처럼 볼수 있다.
public class ProductDaoImpl implements ProductDao { private SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } public Collection loadProductsByCategory(String category) { return this.sessionFactory.getCurrentSession() .createQuery("from test.Product product where product.category=?") .setParameter(0, category) .list(); } }
이 Hibernate 접근 스타일은 인스턴스 변수내 SessionFactory를 유지하는 것을 제외하고 당신이 Hibernate문서와 예제에서 찾을것과 매우 유사하다. 우리는 Hibernate의 CaveatEmptor 샘플 애플리케이션에서 보수적인 정적(static) HibernateUtil 클래스 곳곳에서 인스턴스-기반 셋업같은것을 강력하게 추천한다. (대개, 무조건적으로 필요하지 않는한, 정적(static) 변수내 어떠한 자원도 유지하지 말라.)
위 DAO는 의존성삽입 패턴을 따른다. 이것이 Spring의 HibernateTemplate에 대해 코딩된것처럼 이것은 여전히 Spring 애플리케이션 컨텍스트로 잘 끼워진다(fit). 명백히, 이것은 Setter삽입을 사용한다. 원한다면, 이것은 대신 생성자 삽입을 사용할수 있다. 물론 이러한 DAO는 명백한 Java(예를 들어, 단위 테스트내)로 셋업될수 있다. 이것을 간단히 인스턴스화하고 요구되는 factory참조를 가진 setSessionFactory를 호출한다. Spring bean정의는 다음과 같을것이다.
<beans> ... <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans>
이 DAO스타일의 가장 큰 장점은 Hibernate API에만 의존한다(import를 요구하는 Spring클래스는 없다.)는 것이다. 이것은 물론 침략하지 않는(non-invasiveness) 관점을 요구하고 Hibernate개발자에게는 좀더 자연스럽게 느껴질것이다.
어쨌든, DAO는 비록 Hibernate 자체의 예외 구조에 의존하기를 원하지 않더라도 호출자가 치명적인 것처럼 예외를 처리할수 있다는 것을 의미하는 명백한 HibernateException(체크되지 않았고, 그래서 선언되거나 catch되어야만 한다.)를 던진다. 최적화된 락 실패와 같은 것의 발생을 잡는것은 구현 전략에 호출자를 묶는 것을 시도하지 않고서는 가능하지 않다. 이 교환은 강력한 Hibernate-기반 그리고/또는 특별한 예외 처리가 필요하지 않은 애플리케이션에 받아들여질수 있을것이다.
운이 좋게도, Spring의 LocalSessionFactoryBean은 HibernateTransactionManager를 가진 현재의 Spring-기반 트랜잭션 성질의 Session을 반환하는 Spring트랜잭션 전략을 위해 Hibernate의 SessionFactory.getCurrentSession()메소드를 지원한다. 물론 이 메소드의 표준 행위가 남아있다. 만약 그렇다면(Spring의 JtaTransactionManager, EJB CMT 또는 명백한 JTA에 의해 다루어지는지는 상관없이) 진행중인 JTA트랜잭션과 관련된 현재 Session을 반환한다.
요약하면, Spring관리 트랜잭션에 참여할수 있는 동안 DAO는 명백한 Hibernate 3 API에 기초를 두고 구현될수 있다. 이것은 이미 Hibernate에 친숙한 사람에게 매력이 될것이다. 어쨌든, 이러한 DAO는 명백한 HibernateException를 던질것이다. Spring의 DataAccessException로의 변환은 명시적으로 발생해야만 한다.
하위 레벨의 데이터 접근 서비스의 가장 상위에서 트랜잭션은 애플리케이션의 더 놓은 레벨내에서 구분될수 있다. 여기서는 또한 비지니스 서비스의 구현에서 어떠한 제한도 없다. 이것은 단지 Spring의 PlatformTransactionManager만을 필요로 한다. 나중에 어디서부터든지 올수(호출할수?) 있지만 마치 productDAO이 setProductDao메소드를 통해서 생성이 되듯이 setTransactionManager메소드를 통해 빈 참조처럼 될수도 있다. 다음의 조각(snippets)은 트랜잭션 관리자와 Spring 애플리케이션 컨텍스트내에서 비지니스 서비스 정의를 보여준다. 그리고 비지니스 메소드의 구현을 위한 예제를 보여준다.
<beans> ... <bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> <bean id="myProductService" class="product.ProductServiceImpl"> <property name="transactionManager" ref="myTxManager"/> <property name="productDao" ref="myProductDao"/> </bean> </beans>
public class ProductServiceImpl implements ProductService {
private PlatformTransactionManager transactionManager;
private ProductDao productDao;
public void setTransactionManager(PlatformTransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public void setProductDao(ProductDao productDao) {
this.productDao = productDao;
}
public void increasePriceOfAllProductsInCategory(final String category) {
TransactionTemplate transactionTemplate = new TransactionTemplate(this.transactionManager);
transactionTemplate.execute(
new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
List productsToChange = productDAO.loadProductsByCategory(category);
// do the price increase...
}
}
);
}
}
대신에, Spring 컨테이너 XML파일내 설정된 AOP트랜잭션 인터셉터를 가지는 Java코드에서 명시적 트랜잭션 선언 API호출을 대체하는 것을 가능하게 하는 Spring의 선언적인 트랜잭션 지원을 사용할수 있다. 이것은 당신에게 반복적인 트랜잭션 선언 코드의 비지니스 서비스를 유지하는 것을 허용하고 비지니스 로직을 추가하는 것에 집중하도록 허용한다. 게다가 전달행위와 격리레벨같은 트랜잭션 구문은 설정파일내에서 변경될수도 있다. 그리고 비지니스 서비스 구현에는 어떠한 영향도 끼치지 않는다.
<beans>
...
<bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<bean id="myProductService" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="product.ProductService"/>
<property name="target">
<bean class="product.DefaultProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>myTxInterceptor</value> <!-- the transaction interceptor (configured elsewhere) -->
</list>
</property>
</bean>
</beans>
public class ProductServiceImpl implements ProductService { private ProductDao productDao; public void setProductDao(ProductDao productDao) { this.productDao = productDao; } // notice the absence of transaction demarcation code in this method // Spring's declarative transaction infrastructure will be demarcating transactions on your behalf public void increasePriceOfAllProductsInCategory(final String category) { List productsToChange = this.productDAO.loadProductsByCategory(category); ... } ... }
Spring의 TransactionInterceptor는 TransactionTemplate이 callback내에서 체크되지 않은 exception에 제한적인 동안 callback코드내에 던져진 체크된 애플리케이션 exception을 허락한다. TransactionTemplate는 체크되지 않은 애플리케이션 exception의 경우이거나 애플리케이션에 의해 rollback-only일 경우에 롤백처리를 한다. TransactionTemplate은 체크되지 않은 애플리케이션 예외일 경우 롤백을 유발하거나 트랜잭션이 애플리케이션(TransactionStatus을 통해)에 의해 롤백만을 수행하도록 되어 있다면 TransactionInterceptor는 디폴트에 의해 같은 방식으로 작동하지만 메소드별로 설정가능한 롤백 정책을 허락한다.
선언적인 트랜잭션을 위한 다음의 더 높은 레벨의 접근법은 ProxyFactoryBean를 사용하지 않고 트랜잭션형태를 가지도록 만들길 바라는 많은 서비스 객체를 가진다면 사용하기 더 쉬울것이다.
![]() | Note |
---|---|
당신이 이전의 것을 지속하지 않는다면 Section 9.5, “선언적인 트랜잭션 관리” 부분을 읽도록 강력히 장려한다. |
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
<!-- SessionFactory, DataSource, etc. omitted -->
<bean id="myTxManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
<property name="sessionFactory" ref="mySessionFactory"/>
</bean>
<aop:config>
<aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/>
</aop:config>
<tx:advice id="txAdvice" transaction-manager="myTxManager">
<tx:attributes>
<tx:method name="increasePrice*" propagation="REQUIRED"/>
<tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/>
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
<bean id="myProductService" class="product.SimpleProductService">
<property name="productDao" ref="myProductDao"/>
</bean>
</beans>
TransactionTemplate과 TransactionInterceptor는 Hibernate애플리케이션을 위한 HibernateTransactionManager(ThreadLocal Session을 사용하는 하나의 Hibernate SessionFactory를 위한)나 JtaTransactionManager(컨테이너의 JTA하위 시스템으로 위임하는)가 될수 있는 PlatformTransactionManager인스턴스로 실질적인 트랜잭션 핸들링을 위임한다. 당신은 사용자 정의 PlatformTransactionManager구현을 사용할수도 있다. 그래서 근본적인 Hibernate트랜잭션관리로 부터 JTA로의 전환(예를 들면 당신의 애플리케이션의 어떠한 배치작업을 위한 분산된 트랜잭션 요구사항에 직면했을때)은 설장상의 문제가 된다. Spring의 JTA트랜잭션 구현으로 Hibernate 트랜잭션 관리자를 간단하게 대신한다. 트랜잭션 구분과 데이터 접근 코드는 변경없이 작동할 것이다. 그리고 그들은 일반적인 트랜잭션 관리 API들을 사용한다.
다중 Hibernate session factories를 통한 분산된 트랜잭션을 위해 다중 LocalSessionFactoryBean정의와 함께 트랜잭션 전략처럼 JtaTransactionManager을 간단하게 조합한다. 각각의 DAO들은 그것의 개별적인 빈 프라퍼티로 전달된 하나의 특정 SessionFactory참조를 얻게된다. 모든 근본적인 JDBC데이터 소스는 트랜잭션적인 컨테이너이다. 비지니스 서비스는 전략으로 JtaTransactionManager을 사용하는 한 많은 DAO와 특정 고려없는 많은 session factory를 통해 트랜잭션의 경계를 지정할수 있다.
<beans> <bean id="myDataSource1" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName value="java:comp/env/jdbc/myds1"/> </bean> <bean id="myDataSource2" class="org.springframework.jndi.JndiObjectFactoryBean"> <property name="jndiName" value="java:comp/env/jdbc/myds2"/> </bean> <bean id="mySessionFactory1" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource1"/> <property name="mappingResources"> <list> <value>product.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.MySQLDialect hibernate.show_sql=true </value> </property> </bean> <bean id="mySessionFactory2" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <property name="dataSource" ref="myDataSource2"/> <property name="mappingResources"> <list> <value>inventory.hbm.xml</value> </list> </property> <property name="hibernateProperties"> <value> hibernate.dialect=org.hibernate.dialect.OracleDialect </value> </property> </bean> <bean id="myTxManager" class="org.springframework.transaction.jta.JtaTransactionManager"/> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory1"/> </bean> <bean id="myInventoryDao" class="product.InventoryDaoImpl"> <property name="sessionFactory" ref="mySessionFactory2"/> </bean> <!-- this shows the Spring 1.x style of declarative transaction configuration --> <!-- it is totally supported, 100% legal in Spring 2.x, but see also above for the sleeker, Spring 2.0 style --> <bean id="myProductService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="myTxManager"/> <property name="target"> <bean class="product.ProductServiceImpl"> <property name="productDao" ref="myProductDao"/> <property name="inventoryDao" ref="myInventoryDao"/> </bean> </property> <property name="transactionAttributes"> <props> <prop key="increasePrice*">PROPAGATION_REQUIRED</prop> <prop key="someOtherBusinessMethod">PROPAGATION_REQUIRES_NEW</prop> <prop key="*">PROPAGATION_SUPPORTS,readOnly</prop> </props> </property> </bean> </beans>
HibernateTransactionManager 와 JtaTransactionManager는 트랜잭션 관리자 룩업이나 JCA연결자(트랜잭션을 초기화하기 위해 EJB를 사용하지 않는 한)를 정의하는 컨테이너 없이 Hibernate를 사용하여 적당한 JVM레벨의 캐시 핸들링을 허락한다.
HibernateTransactionManager는 평범한 특정 DataSource를 위한 JDBC접근 코드를 위해 Hibernate의해 사용되는 JDBC Connection을 추출할수 있다. 이것은 하나의 데이터베이스에 접근하는 한 JTA없이 완벽한 Hibernate/JDBC혼합 접근으로 높은 레벨의 트랜잭션 구분을 허락한다. 전달된 SessionFactory가 DataSource(LocalSessionFactoryBean의 "dataSource"를 통해)로 셋업된다면 HibernateTransactionManager는 JDBC트랜잭션처럼 Hibernate트랜잭션을 자동적으로 나타낼것이다. 대신, HibernateTransactionManager의 "dataSource" 프라퍼티를 통해 트랜잭션을 제공하는 DataSource는 명시적으로 선언될수 있다.
Spring의 자원 관리는 애플리케이션 코드의 한줄의 변경도 없이 JNDI SessionFactory와 JNDI DataSource와 같은 로컬 SessionFactory사이의 간단한 전환를 허락한다. 컨테이너내 자원 정의를 유지하거나 애플리케이션 내 로컬 상태로 유지하더라도 사용되는 트랜잭션 전략의 주요한 문제이다. Spring정의 로컬 SessionFactory에 비교하여 수동으로 등록된 JNDI SessionFactory는 어떠한 이득도 제공하지 않는다. Hibernate의 JCA connector를 통해 SessionFactory를 배치하는 것은 J2EE서버의 관리구조내 추가된 값을 제공한다. 하지만 실질적인 값을 추가하지 않는다.
Spring의 트랜잭션 지원의 중요한 이득은 컨테이너에 전혀 바운드 되지 않는 것이다. JTA가 아닌 다른 전략에 설정하는 것은 단독으로 작동하거나 테스트 환경에서도 잘 작동할것이다. 하나의 데이터베이스 트랜잭션의 전형적인 경우를 위해 특별히 이것은 가볍고 JTA에 강력한 대안이다. 트랜잭션을 다루기 위해 로컬 EJB 비상태 유지 세션빈을 사용할때 당신은 비록 하나의 데이터베이스만을 사용하고 CMT를 통해 선언적인 트랜잭션을 위해 SLSB를 사용하더라도 EJB컨테이너와 JTA에 모두 의존한다. 프로그램적으로 JTA를 사용하는것의 대안도 J2EE환경을 요구한다. JTA는 JTA와 JNDI DataSource의 개념에서 컨테이너 의존성을 포함하지 않는다. Spring을 사용하지 않는 JTA에 의도한 Hibernate트랜잭션을 위해 당신은 적당한 JVM레벨의 캐시를 위해 Hibernate JCA연결자를 사용하거나 JTATransaction이 설정된 추가적인 Hibernate트랜잭션 코드를 사용해야 한다.
Spring지향 트랜잭션이 만약 하나의 데이터베이스에 접근한다면 로컬 JDBC DataSource처럼 로컬에 정의된 Hibernate SessionFactory와 잘 작동할수 있다. 그러므로 당신은 분산 트랜잭션 요구사항에 실질적으로 직면했을때 Spring의 JTA트랜잭션 전략으로 물러나야 한다. JCA연결자는 컨테이너 특유의 배치단계를 필요로 하고 명백하게 첫번째 단계에서 JCA지원을 필요로 한다. 이것은 로컬 자원 정의와 Spring이 의도한 트랜잭션과 함께 간단한 웹 애플리케이션을 배치하는것보다 더 괴롭다. 그리고 당신은 종종 컨테이너의 기업용 버전(예를 들면 웹로직 익스프레스 버전은 JCA를 제공하지 않는다.)을 필요로 한다. 로컬 자원과 하나의 데이터베이스를 확장하는 트랜잭션을 가진 Spring애플리케이션은 Tomcat, Resin, 또는 Jetty와 같은 어떠한 J2EE 웹 컨테이너(JTA, JCA, 또는 EJB 없이)내에서도 작동한다. 추가적으로 미들티어같은 것은 데스크탑 애플리케이션이나 테스트 슈트를 쉽게 재사용할수 있다.
모든것을 고려해서 당신이 EJB를 사용하지 않는다면 로컬 SessionFactory 셋팅과 Spring의 HibernateTransactionManager 나 JtaTransactionManager에 충실하라. 당신은 어떠한 컨테이너 배치의 귀찮음 없이 적당한 트랜잭션적인 JVM레벨의 캐싱과 분산 트랜잭션을 포함한 모든 이득을 가질것이다. JCA연결자를 통한 Hibernate SessionFactory의 JNDI등록은 EJB를 사용하기 위해 단지 값만 추가한다.
매우 엄격한 XADataSource구현물을 가지는 몇몇 JTA환경(현재 웹로직과 웹스피어의 몇몇 버전에서만 가능한)에서 JTA PlatformTransactionManager의 인지가 없이 설정된 Hibernate를 사용할때, 애플리케이션 서버 로그를 보여주기 위한 가까 경고나 예외가 가능하다. 이러한 경고와 예외는 트랜잭션이 더이상 활성화되지 않기 때문에 접속되는 connection이나 JDBC접근이 더이상 유효하지 않도록 영향을 주는 것들에 대해 말해줄것이다. 예제에서, 이것은 웹로직에서의 실질적인 예외이다.
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No further JDBC access is allowed within this transaction.
이 경고는 동기화(Spring과 함께)하기 위해 Hibernate가 JTA PlatformTransactionManager 인스턴스를 인지하도록 하여 분석하기 쉽다. 이것은 두가지 방법으로 수행될것이다.
만약 당신의 애플리케이션 컨텍스트에서 당신이 Spring의 JtaTransactionManager 예제를 위해 JTA PlatformTransactionManager를 직접적으로 얻고 이것을 공급한다면, 가장 쉬운 방법은 LocalSessionFactoryBean의 jtaTransactionManager프라퍼티의 값으로 이것에 대한 참조를 간단히 명시한다. Spring은 Hibernate를 위해 사용가능한 객체를 만들것이다.
당신이 JTA PlatformTransactionManager인스턴스를 이미 가지지 않는다(Spring의 JtaTransactionManager는 자체적으로 이것을 찾을수 있기 때문에). 그래서 당신은 이것을 직접적으로 찾기 위해 Hibernate를 설정하는 것을 대신할 필요가 있다. 이것은 Hibernate메뉴얼에서 언급된것처럼, Hibernate설정내 애플리케이션 서버 특정의 TransactionManagerLookup클래스를 설정하여 수행된다.
이것은 임의의 사용법을 위해 좀더 많은 것을 읽는것이 필요할 뿐 아니라 JTA PlatformTransactionManager를 인지하는 Hibernate를 가지거나 가지지 않는 일련의 이벤트가 언급되었다.
Hibernate가 JTA PlatformTransactionManager를 알면서 설정되지 않을때, JTA트랜잭션이 수행하는 일련의 이벤트는 다음과 같다.
JTA 트랜잭션이 커밋된다.
Spring의 JtaTransactionManager는 JTA트랜잭션에 동기화된다. 그래서 이것은 JTA트랜잭션 관리자에 의해 afterCompletion 콜백 메소드를 통해 콜백된다.
활동간에, 이것은 Hibernate의 afterTransactionCompletion 콜백(Hibernate캐시를 지우기 위해 사용되는)을 통해, Hibernate Session에서 명시적으로 Hibernate가 JDBC Connection을 close()하도록 시도하는 결과를 만드는 close() 메소드를 호출하여 Hibernate를 위해 Spring에 의해 콜백을 처리할수 있다.
몇몇 환경에서, 이 Connection.close()호출은 트랜잭션이 이미 커밋된후 애플리케이션 서버가 Connection사용을 더이상 고려하지 않는 것과 같이 경고나 에러를 처리한다.
Hibernate가 JTA PlatformTransactionManager를 알고 설정될때, JTA트랜잭션이 수행하는 일련의 이벤트는 다음과 같다.
JTA 트랜잭션은 커밋할 준비가 된다.
Spring의 JtaTransactionManager는 JTA트랜잭션에 동기화된다. 그래서 이것은 JTA트랜잭션 관리자에 의한 beforeCompletion 콜백 메소드를 통해 콜백된다.
Spring은 Hibernate자체가 JTA트랜잭션으로 동기화되고 이전 시나리오와는 다르게 작동하는 것을 알아차린다. Hibernate Session이 전혀 닫힐 필요가 없다고 가정할때, Spring은 이것을 지금 닫을것이다.
JTA 트랜잭션이 커밋된다.
Hibernate는 JTA트랜잭션에 동기화된다. 그래서 이것은 JTA트랜잭션 관리자에 의한 afterCompletion 콜백메소드를 통해 콜백하고 캐시를 지울수 있다.
Spring은 Hibernate지원처럼 같은 스타일을 따르는 데이터 접근 전략으로 표준 JDO 1.0/2.0 API를 지원한다. 관련 통합 클래스는 org.springframework.orm.jdo 패키지에 존재한다.
Spring은 Spring 애플리케이션 컨텍스트내 local JDO PersistenceManagerFactory를 정의하는 것을 허용하는 LocalPersistenceManagerFactoryBean 클래스를 제공한다.
<beans> <bean id="myPmf" class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean"> <property name="configLocation" value="classpath:kodo.properties"/> </bean> ... </beans>
대신, PersistenceManagerFactory는 PersistenceManagerFactory 구현물 클래스를 직접 인스턴스화하는 것을 통해 셋업될수 있다. JDO PersistenceManagerFactory 구현물 클래스는 Spring bean정의에 자연스럽게 맞아 떨어지는 JDBC DataSource 구현물 클래스처럼 자바빈 패턴을 따르기 위해 제공된다. 이 셋업 스타일은 "connectionFactory" 프라퍼티로 전달되는 Spring-정의 JDBC DataSource를 언제나 지원한다. 예를 들어, 오픈 소스 JDO구현물인 JPOX(http://www.jpox.org):
<beans> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="myPmf" class="org.jpox.PersistenceManagerFactoryImpl" destroy-method="close"> <property name="connectionFactory" ref="dataSource"/> <property name="nontransactionalRead" value="true"/> </bean> ... </beans>
JDO PersistenceManagerFactory는 대개 특정 JDO구현물이 제공하는 JCA connector를 통해 J2EE애플리케이션 서버의 JNDI환경으로 셋업될수 있다. Spring의 표준 JndiObjectFactoryBean은 PersistenceManagerFactory와 같은 것을 가져오고 나타내기 위해 사용될수 있다. 어쨌든, EJB컨텍스트 외부에는, JNDI내 PersistenceManagerFactory를 유지하는 이득을 종종 앞도하지 않는다. 검토를 위해 Hibernate섹션내 "컨테이너 resource 대 local resource"를 보라. 인자들은 JDO에도 잘 적용된다.
각각의 JDO기반 DAO는 의존성 삽입(이를테면, bean프라퍼티 setter나 생성자의 인자를 통해서)을 통해 PersistenceManagerFactory를 가져올것이다. DAO는 주어진 PersistenceManagerFactory와 작동하는 명백한 JDO API에 대해 코딩될수 있다. 하지만 Spring의 JdoTemplate과 사용될것이다. :
<beans> ... <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="persistenceManagerFactory" ref="myPmf"/> </bean> </beans>
public class ProductDaoImpl implements ProductDao { private PersistenceManagerFactory persistenceManagerFactory; public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) { this.persistenceManagerFactory = pmf; } public Collection loadProductsByCategory(final String category) throws DataAccessException { JdoTemplate jdoTemplate = new JdoTemplate(this.persistenceManagerFactory); return (Collection) jdoTemplate.execute(new JdoCallback() { public Object doInJdo(PersistenceManager pm) throws JDOException { Query query = pm.newQuery(Product.class, "category = pCategory"); query.declareParameters("String pCategory"); List result = query.execute(category); // do some further stuff with the result list return result; } }); } }
콜백 구현물은 JDO데이터 접근을 위해 효과적으로 사용될수 있다. JdoTemplate은 PersistenceManager가 열렸는지 닫혔는지를 책임지고 트랜잭션내 자동으로 포함될것이다. 템플릿 인스턴스는 쓰레드에 안전하고(thread-safe) 재사용가능하다. 게다가 클래스 전역의 인스턴스 변수처럼 유지될수 있다. find, load, makePersistent, 또는 delete 호출중 하나와 같은 간단히 한개의 단계를 가진 실행을 위해, JdoTemplate은 한줄의 콜백 구현물처럼 대체될수 있는 대안인 편리한 메소드를 제공한다. 게다가, Spring은 PersistenceManagerFactory를 가져오기 위한 setPersistenceManagerFactory메소드와 하위 클래스에 의해 사용하기 위한 getPersistenceManagerFactory 과 getJdoTemplate을 가진 편리한 JdoDaoSupport base클래스를 제공한다. 복합적으로, 이것은 대개의 요구사항을 위한 간단한 DAO구현물을 허용한다.
public class ProductDaoImpl extends JdoDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { return getJdoTemplate().find( Product.class, "category = pCategory", "String category", new Object[] {category}); } }
Spring의 JdoTemplate과 작동하는 대신에, 당신은 명시적으로 PersistenceManager를 열고 닫는 JDO API레벨에서 Spring기반의 DAO를 코딩할수 있다. 관련 Hibernate섹션에서 상세히 설명된것처럼, 이 접근법의 가장 중요한 장점은 당신의 데이터 접근코드가 체크된 예외를 던질수 있다는 것이다. JdoDaoSupport는 예외를 변환하는 것만큼 트랜잭션 성질을 가진 PersistenceManager을 가져오고 해제하기 위한 이러한 시나리오는 위해 다양한 지원 메소드를 제공한다.
DAO는 Spring의존성 없이 삽입된 PersistenceManagerFactory를 직접 사용하여 명백한 JDO API에 대해 작성될수 있다. 관련 DAO 구현물은 다음과 같을것이다.
public class ProductDaoImpl implements ProductDao { private PersistenceManagerFactory persistenceManagerFactory; public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) { this.persistenceManagerFactory = pmf; } public Collection loadProductsByCategory(String category) { PersistenceManager pm = this.persistenceManagerFactory.getPersistenceManager(); try { Query query = pm.newQuery(Product.class, "category = pCategory"); query.declareParameters("String pCategory"); return query.execute(category); } finally { pm.close(); } } }
의존성 삽입 패턴을 따르는 위 DAO처럼, 이것은 Spring의 JdoTemplate에 대해 코딩된다면 Spring 애플리케이션 컨텍스트에 잘 맞다.
<beans> ... <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="persistenceManagerFactory" ref="myPmf"/> </bean> </beans>
이런 DAO에 대한 중요한 이슈는 DAO들이 factory로 부터 언제나 새로운 PersistenceManager를 가진다는 것이다. Spring-관리 트랜잭션 성질을 가진 PersistenceManager에 접근하기 위해, 당신의 목표 PersistenceManagerFactory앞에서 당신의 DAO로 프록시를 전달하는 TransactionAwarePersistenceManagerFactoryProxy(Spring에 포함된것처럼)를 정의하는 것을 고려해보라.
<beans> ... <bean id="myPmfProxy" class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy"> <property name="targetPersistenceManagerFactory" ref="myPmf"/> </bean> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="persistenceManagerFactory" ref="myPmfProxy"/> </bean> ... </beans>
당신의 데이터 접근 코드는 PersistenceManagerFactory.getPersistenceManager()메소드로 부터 트랜잭션 성질을 가지는 PersistenceManager를 가져올것이다. 후자의 메소드는 factory로부터 새로운 것을 얻기 전에 현재의 트랜잭션 성질을 가지는 PersistenceManager를 먼저 체크할 프록시를 통해 호출한다. PersistenceManager에서의 close()호출은 트랜잭션 PersistenceManager의 경우 무시될것이다.
만약 당신의 데이터 접근 코드가 활성화된 트랜잭션내에서 수행될것이라면, 당신의 DAO구현물이 간결하도록 유지하기 위해 선호할 PersistenceManager.close()호출과 전체 finally블럭을 생략하는 것이 안전하다.
public class ProductDaoImpl implements ProductDao { private PersistenceManagerFactory persistenceManagerFactory; public void setPersistenceManagerFactory(PersistenceManagerFactory pmf) { this.persistenceManagerFactory = pmf; } public Collection loadProductsByCategory(String category) { PersistenceManager pm = this.persistenceManagerFactory.getPersistenceManager(); Query query = pm.newQuery(Product.class, "category = pCategory"); query.declareParameters("String pCategory"); return query.execute(category); } }
활성화된 트랜잭션을 의존하는 DAO와 함께, TransactionAwarePersistenceManagerFactoryProxy의 "allowCreate" 플래그를 꺼서 활성화된 트랜잭션을 강요하는 것이 추천된다.
<beans> ... <bean id="myPmfProxy" class="org.springframework.orm.jdo.TransactionAwarePersistenceManagerFactoryProxy"> <property name="targetPersistenceManagerFactory" ref="myPmf"/> <property name="allowCreate" value="false"/> </bean> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="persistenceManagerFactory" ref="myPmfProxy"/> </bean> ... </beans>
이러한 DAO스타일의 가장 큰 장점은 오직 JDO API에 의존적인 것이다. 요구되는 Spring클래스 import는 없다. 이것은 물론 침략적이지 않은(non-invasiveness) 관점을 요구하고 JDO개발자에게 좀더 자연스럽게 느껴질것이다.
어쨌든, DAO는 호출자가 비록 JDO자체의 예외 구조에 의존하길 원하지 않더라도 치명적인 것처럼 예외를 처리할수 있다는 것을 의미하는 명백한 JDOException(체크되지 않은, 그래서 선언되지 않거나 catch되지 않는)을 던진다. 최적화 락 실패와 같은 것을 야기하는 것을 잡는것은 구현 전략에 호출자를 묶지 않고서는 불가능하다. 이 교환(tradeoff)은 강력하게 JDO기반이고/이거나 어느 특별한 예외 처리가 필요하지 않은 애플리케이션에 받아들일수 있을것이다.
요약해서, DAO는 Spring관리 트랜잭션에서 작동할수 있는 반면에 명백한 JDO API에 기초를 두고 구현될수 있다. 이것은 JDO에 이미 친숙한 사람에게 특히 매력적이다. 어쨌든, 이러한 DAO는 명백한 JDOException를 던질것이다. Sprnig의 DataAccessException로의 변환은 명시적으로 발생할것이다(물론 원한다면).
트랜잭션내에서 서비스 작업을 수행하기 위해, 당신은 Spring의 공통적인 선언적 트랜잭션 기능을 사용할수 있다. 예를 들어,
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> ... <bean id="myTxManager" class="org.springframework.orm.jdo.JdoTransactionManager"> <property name="persistenceManagerFactory" ref="myPmf"/> </bean> <bean id="myProductService" class="product.ProductServiceImpl"> <property name="productDao" ref="myProductDao"/> </bean> <tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <tx:method name="increasePrice*" propagation="REQUIRED"/> <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/> </aop:config> </beans>
JDO는 영속성 객체를 변경할때 활성화된 트랜잭션을 요구한다. Hibernate와는 반대로, JDO에는 비-트랜잭션 속성의(non-transactional) flush와 같은 개념은 없다. 이러한 이유로, 선택된 JDO구현물은 특정 환경을 위해 셋업될 필요가 있다. 특히, 활성화된 JTA트랜잭션 자체를 감지하는 JTA동기화를 위해 셋업될 필요가 있다. 이것은 Spring의 JdoTransactionManager가 수행하는 것과 같이 local트랜잭션을 위해 필요하지 않다. 하지만 (Spring의 JtaTransactionManager나 EJB CMT / 명백한 JTA에 의해 다루어지는) JTA트랜잭션에 포함될 필요가 있다.
JdoTransactionManager는 같은 JDBC DataSource에 접근하는 JDBC접근 코드에 대해 JDO트랜잭션을 나타낼수 있다. 등록된 JdoDialect는 참조하는 JDBC Connection의 획득을 지원한다. 이것은 디폴트에 의해 JDBC-기반 JDO 2.0구현물을 위한 경우이다. JDO 1.0구현물을 위해, 사용자 정의 JdoDialect는 사용될 필요가 있다. JdoDialect 기법에 대한 상세한 정보를 위해서 다음 섹션을 보라.
고급 기능으로, JdoTemplate 과 JdoTransactionManager는 "jdoDialect" bean프라퍼티로 전달되는 사용자정의 JdoDialect를 지원한다. 이런 시나리오에서, DAO는 JdoTemplate인스턴스 대신에 PersistenceManagerFactory참조를 받지 않을것이다. JdoDialect구현물은 언제나 업체 종속적인 방법으로 Spring이 제공하는 몇가지 고급기능을 가능하게 할수 있다.
특정 트랜잭션 성질 적용하기(사용자정의 격리레벨 이나 트랜잭션 타임아웃과 같은)
트랜잭션 성질을 가진 JDBC Connection 가져오기(JDBC기반 DAO에 나타내기 위해)
쿼리 타임아웃 적용하기(Spring관리 트랜잭션 타임아웃으로부터 자동으로 계산된)
PersistenceManager를 열심히 flush하기(눈에 보이는 트랜잭션 성질을 JDBC기반 데이터 접근 코드에 변경하기)
Spring DataAccessExceptions를 위한 JDOExceptions의 고급 번역
이러한 기능은 표준 API에 의해 다루어지지 않는 JDO 1.0구현물을 위해 특히 가치있다. JDO 2.0에서, 대부분의 기능은 표준적인 방법으로 지원된다. 나아가 Spring의 DefaultJdoDialect 은 디폴트로 관련 JDO 2.0 API 메소드를 사용한다(Spring 1.2에서). 특별한 트랜잭션 성질과 향상된 예외 분석을 위해, 이것은 업체 종속적인 JdoDialect 하위클래스를 끌어내기 위해 가치있다.
기능과 이 기능이 Spring의 JDO지원내에서 사용되는 방법에 대한 좀더 상세한 정보를 위해서 JdoDialect JavaDoc을 보라.
Spring 1.2 이후, Spring은 Hibernate지원과 같은 스타일을 따르는 데이터 접근 전략처럼 Oracle TopLink (http://www.oracle.com/technology/products/ias/toplink)를 지원한다. TopLink 9.0.4 (Spring 1.2에서 정식제품(production) 버전) 와 10.1.3(Spring 1.2에서 여전히 베타인) 모두 지원된다. 관련 통합 클래스는 org.springframework.orm.toplink패키지에 위치한다.
Spring의 TopLink 지원은 Oracle TopLink팀과 함께 개발되었다. TopLink팀에게 매우 감사한다. 특히 모든 영역에 상세하게 도와준 Jim Clark에게 특히 감사한다.
TopLink 자체는 SessionFactory추상화를 가지지 않는다. 대신, 다중 쓰레드 접근은 하나의 쓰레드 사용을 위해 순서대로 ClientSession를 일으킬수있는 중심적인 ServerSession 개념에 기초한다. 유연한 셋업 옵션을 위해, Spring은 다른 Session 생성 전략간에 전환을 가능하게 하는 TopLink를 위한 SessionFactory 추상화를 정의한다.
한군데에서 모든것이 준비된 가게처럼, Spring은 bean스타일 설정을 가진 TopLink SessionFactory를 정의하는 것을 허용하는 LocalSessionFactoryBean클래스를 제공한다. 이것은 TopLink세션 설정 파일의 위치를 가지고 설정될 필요가 있고 사용하는 Spring-관리 JDBC DataSource를 언제나 가져올것이다.
<beans> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="mySessionFactory" class="org.springframework.orm.toplink.LocalSessionFactoryBean"> <property name="configLocation" value="toplink-sessions.xml"/> <property name="dataSource" ref="dataSource"/> </bean> ... </beans>
<toplink-configuration> <session> <name>Session</name> <project-xml>toplink-mappings.xml</project-xml> <session-type> <server-session/> </session-type> <enable-logging>true</enable-logging> <logging-options/> </session> </toplink-configuration>
LocalSessionFactoryBean은 다중 쓰레드 TopLink ServerSession 아래에 유지하고 적절한 클라이언트 Session을 생성한다(명백한 Session, 관리되는 ClientSession, 또는 트랜잭션을 인지하는 Session(후자는 Spring의 TopLink지원에 의해 주로 내부적으로 사용된다.)). 이것은 하나의 쓰레드인 TopLink를 유지한다.
각각의 TopLink-기반 DAO는 의존성삽입(이를테면, bean프라퍼티 setter나 생성자의 인자를 통해)을 통해 SessionFactory를 가져올것이다. DAO는 주어진 SessionFactory로부터 Session을 가져오는 명백한 TopLink API에 대해 코딩될수 있지만 Spring의 TopLinkTemplate로 언제나 사용될것이다.
<beans> ... <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> </beans>
public class ProductDaoImpl implements ProductDao { private SessionFactory sessionFactory; public void setSessionFactory(SessionFactory sessionFactory) { this.sessionFactory = sessionFactory; } public Collection loadProductsByCategory(final String category) throws DataAccessException { TopLinkTemplate tlTemplate = new TopLinkTemplate(this.sessionFactory); return (Collection) tlTemplate.execute(new TopLinkCallback() { public Object doInTopLink(Session session) throws TopLinkException { ReadAllQuery findOwnersQuery = new ReadAllQuery(Product.class); findOwnersQuery.addArgument("Category"); ExpressionBuilder builder = this.findOwnersQuery.getExpressionBuilder(); findOwnersQuery.setSelectionCriteria( builder.get("category").like(builder.getParameter("Category"))); Vector args = new Vector(); args.add(category); List result = session.executeQuery(findOwnersQuery, args); // do some further stuff with the result list return result; } }); } }
콜백 구현물은 TopLink데이터 접근을 위해 효과적으로 사용될수 있다. TopLinkTemplate은 Session이 임의로 열렸는지 닫혔는지, 그리고 트랜잭션에 자동으로 포함되는지를 보장할것이다. 템플릿 인스턴스는 쓰레드에 안전하고 재사용가능하다. 인스턴스들은 클래스 전역의 인스턴스 변수처럼 유지될수 있다. executeQuery, readAll, readById, 또는 merge 등의 하나의 호출같은 간단한 한단계의 작동을 위해, JdoTemplate은 한줄의 콜백 구현물과 같은 것을 대체할수 있는 편리한 대체 메소드를 제공한다. 게다가, Spring은 편리한 SessionFactory를 가져오기 위한 setSessionFactory메소드, 하위클래스에 의해 사용하기 위한 getSessionFactory 와 getTopLinkTemplate를 제공하는 TopLinkDaoSupport base클래스를 제공한다. 복합적으로, 이것은 대개의 요구사항을 위한 간단한 DAO구현물을 허용한다.
public class ProductDaoImpl extends TopLinkDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { ReadAllQuery findOwnersQuery = new ReadAllQuery(Product.class); findOwnersQuery.addArgument("Category"); ExpressionBuilder builder = this.findOwnersQuery.getExpressionBuilder(); findOwnersQuery.setSelectionCriteria( builder.get("category").like(builder.getParameter("Category"))); return getTopLinkTemplate().executeQuery(findOwnersQuery, new Object[] {category}); } }</programlisting>
사이드 노트: TopLink query 객체는 쓰레드에 안전하고 DAO내에서 캐시될수 있다. 이를테면, 시작시 생성되고 인스턴스 변수에서 유지된다.
Spring의 TopLinkTemplate과 작동하기 위한 대안으로, 당신은 명시적으로 Session을 열고 닫는 TopLink API에 기초를 둔 TopLink데이터 접근을 코딩할수 있다. Hibernate섹션에서 상세히 설명된것처럼, 이 접근법의 가장 큰 장점은 당신의 데이터 접근 코드가 체크된 예외를 던질수 있다는 것이다. TopLinkDaoSupport는 예외를 변환하는 것만큼 트랜잭션 성질을 가진 Session을 가져오고 해제하는 시나리오를 위한 다양한 지원 메소드를 제공한다.
DAO는 Spring에 대한 의존성없이 삽입된 Session를 직접 사용하여 TopLink API에 대해서 작성될수 있다. LocalSessionFactoryBean에 의해 정의된 SessionFactory에 기초할 후자는 Spring의 TransactionAwareSessionAdapter를 통해 Session타입의 bean참조를 나타낸다.
TopLink의 Session인터페이스에서 정의된 getActiveSession() 메소드는 이러한 시나리오에서 현재의 트랜잭션 성질을 가지는 Session을 반환할것이다. 만약 여기에 활성화된 트랜잭션이 없다면, 오직 읽기전용 접근만 사용하도록 가정하는 공유 TopLink ServerSession을 반환할것이다. 여기에는 현재 트랜잭션에 속한 TopLink UnitOfWork를 반환하는 유사한 getActiveUnitOfWork() 메소드도 있다.
관련 DAO구현물은 다음과 같다.
public class ProductDaoImpl implements ProductDao { private Session session; public void setSession(Session session) { this.session = session; } public Collection loadProductsByCategory(String category) { ReadAllQuery findOwnersQuery = new ReadAllQuery(Product.class); findOwnersQuery.addArgument("Category"); ExpressionBuilder builder = this.findOwnersQuery.getExpressionBuilder(); findOwnersQuery.setSelectionCriteria( builder.get("category").like(builder.getParameter("Category"))); Vector args = new Vector(); args.add(category); return session.getActiveSession().executeQuery(findOwnersQuery, args); } }
의존성 삽입 패턴을 따르는 위 DAO처럼, 이것은 Spring의 TopLinkTemplate에 대해 코딩된것과 유사하게 여전히 Spring애플리케이션 컨텍스트에 잘 맞다. Session 타입에 대한 bean참조를 나타내기 위해 사용되는 Spring의 TransactionAwareSessionAdapter는 DAO전달된다.
<beans> ... <bean id="mySessionAdapter" class="org.springframework.orm.toplink.support.TransactionAwareSessionAdapter"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="session" ref="mySessionAdapter"/> </bean> ... </beans>
이 DAO스타일의 가장 큰 장점은 TopLink API에만 의존하는 것이다. import해야할 Spring 클래스는 없다. 물론 다른 API에 대해 의존성을 가지지 않는다는(non-invasiveness) 관점에서 매력적이고, TopLink개발자에게 좀더 자연스럽게 느껴지도록 한다.
어쨌든, DAO는 호출자가 비록 TopLink자체의 예외 구조에 의존하길 원하지 않더라도 치명적인 것처럼 예외를 처리할수 있다는 것을 의미하는 명백한 TopLinkException(체크되지 않은, 그래서 선언되지 않거나 catch되지 않는)을 던진다. 최적화 락 실패와 같은 것을 야기하는 것을 잡는것은 구현 전략에 호출자를 묶지 않고서는 불가능하다. 이 교환(tradeoff)은 강력하게 TopLink기반이고/이거나 어느 특별한 예외 처리가 필요하지 않은 애플리케이션에 받아들일수 있을것이다.
DAO스타일의 단점은 TopLink의 표준 getActiveSession()기능이 JTA트랜잭션 내에서 작동한다는 것이다. 이것은 특히 local TopLink트랜잭션과 함께하지 않는 다른 트랜잭션 전략과 작동하지 않는다.
운이 좋게도, Spring의 TransactionAwareSessionAdapter는 TopLinkTransactionManager와 함께 현재 Spring이 관리하는 트랜잭션 성질을 가지는 Session을 반환하는 Spring트랜잭션 전략을 위한 TopLink의 Session.getActiveSession() 과 Session.getActiveUnitOfWork() 지원을 가지는 TopLink ServerSession를 위한 관련 프록시를 나타낸다. 물론, 이 메소드의 표준 행위는 남아있다. 만약 그렇다면(Spring의 JtaTransactionManager, EJB CMT, 또는 명백한 JTA에 의해 다루어지는지는 상관없이) 진행중인 JTA트랜잭션과 관련된 현재 Session을 반환한다.
요약해서, DAO는 Spring관리 트랜잭션에서 작동할수 있는 반면에 명백한 TopLink API에 기초를 두고 구현될수 있다. 이것은 TopLink에 이미 친숙한 사람에게 특히 매력적이다. 어쨌든, 이러한 DAO는 명백한 TopLinkException를 던질것이다. Sprnig의 DataAccessException로의 변환은 명시적으로 발생할것이다(물론 원한다면).
트랜잭션내에서 서비스 작업을 수행하기 위해, 당신은 Spring의 공통적인 선언적 트랜잭션 기능을 사용할수 있다. 예를 들어,
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> ... <bean id="myTxManager" class="org.springframework.orm.toplink.TopLinkTransactionManager"> <property name="sessionFactory" ref="mySessionFactory"/> </bean> <bean id="myProductService" class="product.ProductServiceImpl"> <property name="productDao" ref="myProductDao"/> </bean> <aop:config> <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/> </aop:config> <tx:advice id="txAdvice" transaction-manager="myTxManager"> <tx:attributes> <tx:method name="increasePrice*" propagation="REQUIRED"/> <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> </beans>
JDO는 영속성 객체를 변경할때 활성화된 UnitOfWork를 요구한다(당신은 명백한 TopLink Session에 의해 반환되는 객체를 변경하지 말아야 한다. 이러한 객체는 언제나 하위레벨(second-level) 캐시에서 직접 가져오는 읽기전용 객체이다). Hibernate와는 반대로, TopLink에는 비-트랜잭션 속성의(non-transactional) flush와 같은 개념은 없다. 이러한 이유로, 선택된 TopLink구현물은 특정 환경을 위해 셋업될 필요가 있다. 특히, 활성화된 JTA트랜잭션 자체를 감지하고 활성화된 관련 Session 과 UnitOfWork를 나타내는 JTA동기화를 위해 셋업될 필요가 있다. 이것은 Spring의 TopLinkTransactionManager가 수행하는 것과 같이 local트랜잭션을 위해 필요하지 않다. 하지만 (Spring의 JtaTransactionManager나 EJB CMT / 명백한 JTA에 의해 다루어지는) JTA트랜잭션에 포함될 필요가 있다.
당신의 TopLink기반 DAO코드에서, 현재의 UnitOfWork에 접근하기 위한 Session.getActiveUnitOfWork() 메소드를 사용하고 이것을 통해 쓰기(write)작업을 수행하라. 이것은 활성화된 트랜잭션(Spring관리 트랜잭션과 명백한 JTA트랜잭션 모두)내에서만 작동할것이다. 특별한 목적을 위해, 당신은 현재 트랜잭션에는 포함되지 않는 개별적인 UnitOfWork 인스턴스를 가질수 있다. 이것은 거의 필요한 경우가 없다.
TopLinkTransactionManager는 같은 JDBC DataSource에 접근하는 JDBC접근 코드에 대해 TopLink트랜잭션을 나타낼수 있다. 그리고 Top는 백엔드(backend)에서 JDBC와 작동하고 참조하는 JDBC Connection을 나타낼수 있다. 트랜잭션을 나타내기 위한 DataSource는 명시적으로 선언될 필요가 있다. 이것은 자동감지되지 않을것이다.
org.springframework.orm.ibatis패키지를 통해 Spring은 iBATIS SqlMaps(http://www.ibatis.com) 1.x 과 2.x을 지원한다. iBATIS지원은 JDBC 또는 Hibernate처럼 템플릿 스타일 프로그래밍을 지원하는 면에서 JDBC / Hibernate지원과 많은 공통점을 가진다. iBATIS지원은 Spring의 예외구조와 함께 작동하고 당신은 Spring이 가지는 모든 IoC특징을 즐기자.
트랜잭션 관리는 Spring의 표준기능을 통해 다루어질수 있다. JDBC Connection과 다르게 포함되는 특별한 트랜잭션 성질의 자원이 없기 때문에 iBATIS만을 위한 특별한 트랜잭션 전략은 없다. 나아가, Spring의 표준 JDBC DataSourceTransactionManager 나 JtaTransactionManager 으로도 완벽하게 충분하다.
Spring은 iBATIS SqlMaps 1.3 과 2.0 모두를 지원한다. 첫번째 둘 사이의 차이점을 보자.
xml설정 파일이 노드와 속성명에서 조금 변경되었다. 또한 당신이 확장할 필요가 있는 Spring클래스는 몇몇 메소드 명처럼 다르다.
iBATIS SQL Maps를 사용하는 것은 statement와 result map들을 포함하는 SQLMaps설정파일을 생성하는것을 포함한다. Spring은 SqlMapFactoryBean을 사용하여 이것들을 로드하는것을 처리한다.
public class Account { private String name; private String email; public String getName() { return this.name; } public void setName(String name) { this.name = name; } public String getEmail() { return this.email; } public void setEmail(String email) { this.email = email; } }
우리가 이 클래스를 맵핑하길 원한다고 가정하자. 우리는 다음의 SQLMaps를 생성한다. 쿼리를 사용하여 우리는 그들의 이메일 주소를 통해 나중에 사용자를 가져올수 있다. Account.xml:
<sql-map name="Account"> <result-map name="result" class="examples.Account"> <property name="name" column="NAME" columnIndex="1"/> <property name="email" column="EMAIL" columnIndex="2"/> </result-map> <mapped-statement name="getAccountByEmail" result-map="result"> select ACCOUNT.NAME, ACCOUNT.EMAIL from ACCOUNT where ACCOUNT.EMAIL = #value# </mapped-statement> <mapped-statement name="insertAccount"> insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#) </mapped-statement> </sql-map>
Sql Map를 정의한 다음, 우리는 iBATIS를 위한 설정파일을 생성한다. (sqlmap-config.xml):
<sql-map-config> <sql-map resource="example/Account.xml"/> </sql-map-config>
iBATIS는 클래스패스로 부터 자원을 로드한다. 그래서 클래스패스 어딘가에 Account.xml파일을 추가하라.
Spring을 사용할때 우리는 SqlMapFactoryBean을 사용해서 SQLMaps를 매우 쉽게 셋업할수 있다.
<beans> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlMap" class="org.springframework.orm.ibatis.SqlMapFactoryBean"> <property name="configLocation" value="WEB-INF/sqlmap-config.xml"/> </bean> ... </beans>
SqlMapDaoSupport 클래스는 HibernateDaoSupport 과 JdoDaoSupport 과 유사한 지원 클래스를 제공한다. DAO를 구현해보자.
public class SqlMapAccountDao extends SqlMapDaoSupport implements AccountDao { public Account getAccount(String email) throws DataAccessException { return (Account) getSqlMapTemplate().executeQueryForObject("getAccountByEmail", email); } public void insertAccount(Account account) throws DataAccessException { getSqlMapTemplate().executeUpdate("insertAccount", account); } }
당신이 보는 것처럼, 우리는 쿼리를 수행하기 위해 미리 설정된 SqlMapTemplate를 사용하고 있다. Spring은 SqlMapFactoryBean을 사용하여 SqlMap을 초기화한다. 그리고 다음처럼 SqlMapAccountDao를 셋업할때, 당신은 모든것을 셋팅한것이다. 노트 : iBATIS SQL Maps 1.x를 사용할때, JDBC DataSource는 언제나 DAO에 명시된다.
<beans> ... <bean id="accountDao" class="example.SqlMapAccountDao"> <property name="dataSource" ref="dataSource"/> <property name="sqlMap" ref="sqlMap"/> </bean> </beans>
노트 : SqlMapTemplate 인스턴스는 생성자 인자로 DataSource와 SqlMap을 전달해서 직접 생성할수 있다. SqlMapDaoSupport base클래스는 SqlMapTemplate인스턴스를 간단하게 미리 초기화한다.
우리는 iBATIS 2.x를 사용해서 앞의 Account를 맵핑하기를 원한다면 우리는 다음의 SQLMaps Account.xml을 생성할 필요가 있다.
<sqlMap namespace="Account"> <resultMap id="result" class="examples.Account"> <result property="name" column="NAME" columnIndex="1"/> <result property="email" column="EMAIL" columnIndex="2"/> </resultMap> <select id="getAccountByEmail" resultMap="result"> select ACCOUNT.NAME, ACCOUNT.EMAIL from ACCOUNT where ACCOUNT.EMAIL = #value# </select> <insert id="insertAccount"> insert into ACCOUNT (NAME, EMAIL) values (#name#, #email#) </insert> </sqlMap>
iBATIS 2를 위한 설정파일(sqlmap-config.xml)은 극히 적은 부분만 바꾼다.
<sqlMapConfig> <sqlMap resource="example/Account.xml"/> </sqlMapConfig>
iBATIS는 classpath로부터 자원을 로드한다는것을 기억하라. 그래서 classpath 어딘가에 Account.xml파일을 추가하는것을 확인하라.
우리는 Spring애플리케이션 컨텍스트내에서 SqlMapClientFactoryBean을 사용할수 있다. iBATIS SQL Maps 2.x에서, JDBC DataSource는 늦은(lazy) 로딩을 가능하게 하는 SqlMapClientFactoryBean에 언제나 명시된다.
<beans> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <bean id="sqlMapClient" class="org.springframework.orm.ibatis.SqlMapClientFactoryBean"> <property name="configLocation" value="WEB-INF/sqlmap-config.xml"/> <property name="dataSource" ref="dataSource"/> </bean> ... </beans>
SqlMapClientDaoSupport클래스는 SqlMapDaoSupport와 유사한 지원 클래스를 제공한다. 우리는 DAO를 구현하기 위해 이것을 확장한다.
public class SqlMapAccountDao extends SqlMapClientDaoSupport implements AccountDao { public Account getAccount(String email) throws DataAccessException { return (Account) getSqlMapClientTemplate().queryForObject("getAccountByEmail", email); } public void insertAccount(Account account) throws DataAccessException { getSqlMapClientTemplate().update("insertAccount", account); } }
DAO에서, 우리는 애플리케이션 컨텍스트에서 SqlMapAccountDao를 셋업하고 이것을 SqlMapClient인스턴스에 묶은 후에 쿼리를 수행하기 위해 미리 설정된 SqlMapClientTemplate을 사용한다.
<beans> ... <bean id="accountDao" class="example.SqlMapAccountDao"> <property name="sqlMapClient" ref="sqlMapClient"/> </bean> </beans>
노트 : SqlMapTemplate 인스턴스는 생성자의 인자로 SqlMapClient를 전달하여 직업 생성할수 있다. SqlMapClientDaoSupport base클래스는 SqlMapClientTemplate인스턴스를 미리 간단히 초기화한다.
SqlMapClientTemplate은 인자로 사용자정의 SqlMapClientCallback 구현물을 가지는 일반적인 execute 메소드를 제공한다. 이것은 예를 들어, 배치를 위해 사용될수 있다.
public class SqlMapAccountDao extends SqlMapClientDaoSupport implements AccountDao { ... public void insertAccount(Account account) throws DataAccessException { getSqlMapClientTemplate().execute(new SqlMapClientCallback() { public Object doInSqlMapClient(SqlMapExecutor executor) throws SQLException { executor.startBatch(); executor.update("insertAccount", account); executor.update("insertAddress", account.getAddress()); executor.executeBatch(); } }); } }
대개, 본래의 SqlMapExecutor API에 의해 제공되는 이러한 작업의 조합은 콜백에서 사용될수 있다. SQLException은 자동으로 Spring의 포괄적인 DataAccessException구조로 변환될것이다.
DAO는 Spring에 대한 의존성없이 삽입된 SqlMapClient를 직접 사용하여 명백한 iBATIS API에 대해 작성될수 있다. 관련 DAO구현물은 다음처럼 보일것이다.
public class SqlMapAccountDao implements AccountDao { private SqlMapClient sqlMapClient; public void setSqlMapClient(SqlMapClient sqlMapClient) { this.sqlMapClient = sqlMapClient; } public Account getAccount(String email) { try { return (Account) this.sqlMapClient.queryForObject("getAccountByEmail", email); } catch (SQLException ex) { throw new MyDaoException(ex); } } public void insertAccount(Account account) throws DataAccessException { try { this.sqlMapClient.update("insertAccount", account); } catch (SQLException ex) { throw new MyDaoException(ex); } } }
이러한 시나리오에서, iBATIS API에 의해 던져지는 SQLException은 사용자정의 형태(대개, 이것을 자신만의 애플리케이션 특성을 가지는 DAO예외로 포장하는)로 다루어질 필요가 있다. 애플리케이션 컨텍스트로 묶는 것은 명백한 iBATIS기반의 DAO가 여전히 의존성삽입 패턴을 따른다는 사실때문에 이전과 같을것이다.
<beans> ... <bean id="accountDao" class="example.SqlMapAccountDao"> <property name="sqlMapClient" ref="sqlMapClient"/> </bean> </beans>
Spring JPA(org.springframework.orm.jpa 패키지에서 사용가능한)는 추가적인 기능을 제공하기 위한 근본적인 구현물을 인식하는 동안 Hibernate나 JDO와의 통합과 유사항 방법으로 Java Persistence API를 위한 편한 지원을 제공한다.
Spring JPA는JPA EntityManagerFactory를 셋업하는 두가지 방법을 제공한다.
LocalEntityManagerFactoryBean 는 데이터 접근을 위해 JPA를 사용하는 환경을 위해 적합한 EntityManager를 생성한다. factory bean은 JPA PersistenceProvider 자동감지 기법을 사용하고 대부분의 경우 퍼시스턴스 단위 이름만을 요구한다.
<beans> ... <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean"> <property name="persistenceUnitName" value="myPersistenceUnit"/> </bean> ... </beans>
JNDI EntityManagerFactory(예를 들어 JTA환경에서)으로 교체하는 것은 XML설정을 변경의 문제이다.
<beans> ... <jndi:lookup id="entityManagerFactory" jndi-name="jpa/myPersistenceUnit"/> ... </beans>
LocalContainerEntityManagerFactoryBean는 JPA EntityManagerFactory 에 대한 완전한 제어를 제공하고 사용자정의가 요구되는 환경에 적절하다. LocalContainerEntityManagerFactoryBean은 'persistence.xml' 파일에 기초하여 PersistenceUnitInfo 를 생성할것이다. dataSourceLookup 전략과 loadTimeWeaver를 제공한다. JNDI외부의 사용자정의 데이터소스와 작동하고 weaving처리에 대한 제어가 가능하다.
<beans> ... <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="someDataSource"/> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> </property> </bean> </beans>
LoadTimeWeaver 인터페이스는 환경(웹 컨테이너/애플리케이션 서버)에 의존하는 특정 방법으로 플러그되는 JPA ClassTransformer 를 허용하는 Spring-기반의 클래스이다. JDK 5.0 agent을 통해 ClassTransformers를 채우는 것은 대개 효과적이지 않다. 에이전트는 entire virtual machine에 대해 작동하고 로드되는 모든 클래스를 조사한다. 때때로 제품 서버환경에서는 적절하지 않다.
Spring은 다양한 환경을 위해 많은 수의 LoadTimeWeaver 구현물을 제공한다. VM별로가 아닌 오직 클래스로더 별로 적용되는 ClassTransformer 를 허용한다.
Jakarta Tomcat의 디폴트 클래스로더는 사용되는 사용자정의 클래스로더를 허용하지만 클래스 번형은 지원하지 않는다. Spring은 Tomcat 클래스로더(WebappClassLoader)를 확장하는 TomcatInstrumentableClassLoader(org.springframework.instrument.classloading.tomcat 패키지내)를 제공하고 JPA ClassTransformer 인스턴스를 로드되는 모든 클래스를 '강화하도록' 허용한다. 짧게, JPA 변형자는 오직 웹 애플리케이션(TomcatInstrumentableClassLoader를 사용하는)내부에서만 적용될것이다.
사용자정의 클래스로더를 사용하기 위해
spring-tomcat-weaver.jar를 $CATALINA_HOME/server/lib($CATALINA_HOME는 Tomcat 설치 디렉토리를 표시한다)로 복사한다.
웹 애플리케이션 컨텍스트 파일을 편집하여 Tomcat에 사용자정의 클래스로더를 사용하도록 지시한다.
<Context path="/myWebApp" docBase="/my/webApp/location" ...> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/> ... </Context>
Tomcat 5.0.x 과 5.5.x 시리즈는 여러개의 컨텍스트 위치(서버 설정파일인 ($CATALINA_HOME/conf/server.xml), 배치된 모든 웹 애플리케이션에 영향을 끼치는 전역위치($CATALINA_HOME/conf/context.xml)와 서버에 배치된 웹 애플리케이션별 설정 ($CATALINA_HOME/conf/[enginename]/[hostname]/my-webapp-context.xml) 또는 웹 애플리케이션에 따르는 것(your-webapp.war/META-INF/context.xml))를 지원한다. 효율성을 위해, 웹 애플리케이션 내부의 설정 스타일은 JPA가 사용자정의 클래스로더를 사용할 애플리케이션일때만 추천된다. 사용가능한 컨텍스트 위치에 대한 좀더 상세한 정보를 위해서는 Tomcat 5.x 문서를 보라.
이 시점(Tomcat 5.5.17)에서, server.xml 내부의 Loader 태그의 사용시 XML설정 파싱에 버그가 있다.
Tomcat 4.x에서, 같은 context.xml파일을 사용할수 있고 $CATALINA_HOME/webapps 밑에 두거나 디폴트에 의해 사용자 정의 클래스로더를 사용하는 $CATALINA_HOME/conf/server.xml을 변경할수 있다. 좀더 많은 정보를 위해서 Tomcat 4.x documentation를 보라.
LocalContainerEntityManagerFactoryBean 를 설정할때 적절한 LoadTimeWeaver를 사용하라.
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> ... <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/> </property> ... </bean>
이 기술을 사용하여 에이전트가 필요하지 않은 Tomcat에서 작동할수 있다. 이것은 JPA변형자가 적용된이후 다른 JPA구현물에 의존하는 애플리케이션을 호스팅할때 특히 중요하다. 오직 클래스로더 레벨에서 각각은 격리된다.
Oracle의 OC4J 클래스로더는 원시 바이트코드 번형 지원을 가지는 것처럼, JDK에이전트에서 LoadTimeWeaver로의 전환은 애플리케이션 Spring설정을 통해 수행될수 있다.
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> ... <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.oc4j.OC4JLoadTimeWeaver"/> </property> ... </bean>
클래스가 필요하지만 LoadTimeWeaver구현물에 의해 지원되지 않는 환경을 위해, JDK에이전트는 유일한 해결법이 될수 있다. 이러한 경우를 위해, Spring은 Spring특유의 VM에이전트(spring-agent.jar)를 필요로 하는 InstrumentationLoadTimeWeaver를 제공한다.
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> </property> </bean>
가상머신은 다음의 JVM옵션을 제공하여 Spring에이전트로 시작되어야만 하는 것을 노트하라.
-javaagent:/path/to/spring-agent.jar
다중 퍼시스턴스 유닛 위치(예를 들면 classpath내 다양한 jar파일로 저장된)에 의존하는 애플리케이션을 위해, Spring은 중심적인 저장소처럼 작동하고 퍼시스턴스 유닛 복구 처리를 피하기 위한 PersistenceUnitManager를 제공한다. 디폴트 구현물은 파싱되고 퍼시스턴스 유닛 이름을 통해 나중에 가져오는 명시된 다중 위치(디폴트에 의해, classpath는 META-INF/persistence.xml 파일을 위해 검색된다.)를 허용한다.
<bean id="persistenceUnitManager" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"> <property name="persistenceXmlLocation"> <list> <value>org/springframework/orm/jpa/domain/persistence-multi.xml</value> <value>classpath:/my/package/**/custom-persistence.xml</value> <value>classpath*:META-INF/persistence.xml</value> </list> </property> <property> <map> <entry key="localDataSource" value-ref="local-db"/> <entry key="remoteDataSource" value-ref="remote-db"/> </map> </property> <!-- if no datasource is specified, use this one --> <property name="defaultDataSource" ref="remoteDataSource"/> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitManager" ref="persistenceUnitManager"/> ... </bean>
디폴트 구현물은 프라퍼티나 프로그램으로 처리하여 선언적으로나 PersistenceUnitPostProcessor를 통해 JPA제공자에게 제공하기 전에 퍼시스턴스 유닛 정보의 사용자정의를 허용한다. persistenceUnitManager가 명시되지 않았다면, LocalContainerEntityManagerFactoryBean에 의해 내부적으로 생성되고 사용될것이다.
각각의 JPA기반의 DAO는 의존성 삽입을 통해 EntityManagerFactory를 받을것이다. 이러한 DAO는 명백한 JPA에 대해 코딩되고 주어진 EntityManagerFactory이나 Spring의 JpaTemplate과 작동한다.
<beans> ... <bean id="myProductDao" class="product.ProductDaoImpl"> <property name="entityManagerFactory" ref="entityManagerFactory"/> </bean> ... </beans>
public class JpaProductDao implements ProductDao { private EntityManagerFactory entityManagerFactory; public void setEntityManagerFactory(EntityManagerFactory emf) { this.entityManagerFactoryeManagerFactory = emf; } public Collection loadProductsByCategory(final String category) throws DataAccessException { JpaTemplate jpaTemplate = new JpaTemplate(this.entityManagerFactory); return (Collection) jpaTemplate.execute(new JpaCallback() { public Object doInJpa(EntityManager em) throws PersistenceException { Query query = em.createQuery("from Product as p where p.category = :category"); query.setParameter("category", category); List result = query.getResultList(); // do some further processing with the result list return result; } }); } }
JpaCallback 구현물은 어떠한 JPA 데이터 접근도 허용한다. JpaTemplate은 EntityManager이 열리고 닫히고 자동으로 트랜잭션에 참가하는 것을 확인할것이다. 게다가 JpaTemplate은 자원을 정리하고 적절한 트랜잭션을 롤백하도록 만드는 예외를 다룬다. 템플릿 인스턴스는 쓰레드에 안전하고 재사용가능하며 둘러싼 클래스의 인스턴스 변수처럼 유지될수 있다. JpaTemplate은 한줄의 콜백 구현물을 대체할수 있는 대안이 되는 편리한 메소드를 가지고 검색, 로드, 병합 등과 같은 한가지 단계로 이루어진 action을 제공한다.
게다가, Spring은 get/setEntityManagerFactory와 하위클래스에 의해 사용되는 getJpaTemplate()을 제공하는 편리한 JpaDaoSupport 기본 클래스를 제공한다.
public class ProductDaoImpl extends JpaDaoSupport implements ProductDao { public Collection loadProductsByCategory(String category) throws DataAccessException { Map<String, String> params = new HashMap<String, String>(); params.put("category", category); return getJpaTemplate().findByNamedParams("from Product as p where p.category = :category", params); } }
Spring의 JpaTemplate과 작동하는 것외에도, 자체적으로 명시적인 EntityManager 핸들링하여 JPA에 대해 Spring-기반의 DAO를 코딩할수 있다. 관련된 Hibernate부분에서 고안된것처럼, 이 접근법의 중요한 장점은 당신의 데이터 접근 코드가 체크된 예외를 던질수 있다는 것이다. JpaDaoSupport는 트랜잭션 EntityManager 를 가져오고 풀어주기 위해, 이 시나리오를 위한 다양한 지원 메소드를 제공한다.
![]() | Note |
---|---|
EntityManagerFactory인스턴스가 쓰레드에 안전한데 반해, EntityManager 인스턴스는 그렇지 않다. 삽입된 JPA EntityManager는 JPA스펙에 의해 정의된것처럼, 애플리케이션 서버의 JNDI환경으로 부터 얻어낸 EntityManager처럼 작동한다. 이것은 현재 트랜잭션 속성을 가지는 EntityManager에 대한 모든 호출을 위임할것이다. 이것은 작업마다 새롭게 생성된 EntityManager를 되돌리고 쓰레드에 안전하게 만든다. |
삽입된 EntityManagerFactory 나 EntityManager를 사용하여, 어떤 Spring 의존성을 사용하는것없이 명백한 JPA에 대해 코드를 작성하는 것이 가능하다. Spring은 PersistenceAnnotationBeanPostProcessor가 가능하다면 필드와 메소드 레벨에서 @PersistenceUnit 과 @PersistenceContext 어노테이션을 이해할수 있다. 관련 DAO구현물은 다음과 같을것이다.
public class ProductDaoImpl implements ProductDao { @PersistenceUnit private EntityManagerFactory entityManagerFactory; public Collection loadProductsByCategory(String category) { EntityManager em = this.entityManagerFactory.getEntityManager(); try { Query query = em.createQuery("from Product as p where p.category = ?1"); query.setParameter(1, category); return query.getResultList(); } finally { if (em != null) { em.close(); } } } }
Spring의 JpaTemplate에 대해 코딩된다면 위 DAO는 Spring에 대한 의존성을 가지지 않고 Spring 애플리케이션 컨텍스트에 잘 맞는다. 게다가, DAO는 디폴트 EntityManagerFactory의 삽입을 요구하는 어노테이션의 장점을 가진다.
<beans>
...
<!-- JPA annotations bean post processor -->
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
이러한 DAO를 가진 중요한 이슈는 이것이 언제나 factory로부터 새로운 EntityManager를 얻는다는 것이다. 이것은 factory대신에 삽입되는EntityManager를 요청하여 쉽게 극복될수 있다.
public class ProductDaoImpl implements ProductDao { private EntityManager em; @PersistenceContext public void setEntityManager(EntityManager em) { this.em = em; } public Collection loadProductsByCategory(String category) { Query query = em.createQuery("from Product as p where p.category = :category"); query.setParameter("category", category); return query.getResultList(); } }
삽입된 EntityManager는 Spring관리(진행중인 트랜잭션을 인지하는)된다. 비록 새로운 구현물이 메소드 레벨의 삽입(EntityManagerFactory대신에 EntityManager)을 선호하더라도, 어노테이션 사용으로 인해 애플리케이션 컨텍스트 XML내 요구되는 변경이 없다는 것을 노트하는 것이 중요하다.
이 DAO스타일의 중요한 장점은 Java 퍼시스턴스 API에 의존하는 것이다. Spring클래스의 import가 요구되는 것은 없다. 게다가, JPA어노테이션이 이해되는것처럼, 삽입은 Spring컨테이너에 의해 자동적으로 적용된다. 이것은 물론 비-침략적인 관점에서 호소되고 JPA개발자에 좀더 자연스럽게 느껴질것이다.
게다가, DAO는 명백한 PersistenceException 예외 클래스(체크되지 않았고, 그래서 선언되거나 catch되지 않은)를 던지지만 호출자가 JPA 자체적인 예외 구조에 의존하는 것을 원하는 것을 제외하고 대개 치명적인 예외만을 처리할수 있는 것을 의미하는 IllegalArgumentException 와 IllegalStateException도 던진다. 최적화 락 실패와 같은 특정 사유를 잡는(catch)것은 호출자를 구현전략에 묶는것없이 불가능하다. 이 교환은 JPA-기반과/또는 특별한 예외 처리를 필요로 하지 않는 애플리케이션에 수락될수 있다. 어쨌든, Spring은 @Repository 어노테이션을 통해 투명하게 적용되는 예외 번역을 허용하는 솔루션을 제공한다.
@Repository public class ProductDaoImpl implements ProductDao { ... }
<beans>
...
<!-- Exception translation bean post processor -->
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
<bean id="myProductDao" class="product.ProductDaoImpl"/>
</beans>
후처리자는 모든 예외 번역자(PersistenceExceptionTranslator 인터페이스의 구현물인)와 모든 bean이 @Repository 어노테이션으로 만드는 advice를 자동으로 찾는다. 그래서 발견된 번역자는 가로챌수 있고 던져진 예외에서 적절한 번역을 적용할수 있다.
개요 : DAO는 Spring의 사용자정의 예외 구조를 위한 Spring-관리 트랜잭션, 의존성 삽입 그리고 투명한 예외 변환으로부터 이득이 되는동안 명백한 Java 퍼시스턴스 API와 어노테이션에 기초하여 구현될수 있다.
트랜잭션내 서비스 작업을 수행하기 위해, 당신은 Spring의 공통 선언적인 트랜잭션 기능을 사용할수 있다. 예를 들면:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd"> ... <bean id="myTxManager" class="org.springframework.orm.jpa.JpaTransactionManager"> <property name="entityManagerFactory" ref="myEmf"/> </bean> <bean id="myProductService" class="product.ProductServiceImpl"> <property name="productDao" ref="myProductDao"/> </bean> <aop:config> <aop:pointcut id="productServiceMethods" expression="execution(* product.ProductService.*(..))"/> <aop:advisor advice-ref="txAdvice" pointcut-ref="productServiceMethods"/> </aop:config> <tx:advice id="txAdvice" transaction-manager="myTxManager"> <tx:attributes> <tx:method name="increasePrice*" propagation="REQUIRED"/> <tx:method name="someOtherBusinessMethod" propagation="REQUIRES_NEW"/> <tx:method name="*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> </beans>
Spring JPA는 JPA트랜잭션을 같은 JDBC DataSource에 접근하는 JDBC 접근 코드에 나타내기 위해 설정된 JpaTransactionManager를 허용한다. 등록된 JpaDialect가 근본적인 JDBC Connection의 획득을 지원한다. 특히, Spring은 Toplink와 Hibernate JPA구현물을 위한 dialect를 제공한다. JpaDialect 기법에 대한 상세한 설명을 위해서는 다음 부분을 보라.
JpaTemplate의 고급기능처럼, JpaTransactionManager과 AbstractEntityManagerFactoryBean의 하위클래스는 "jpaDialect" bean프라퍼티에 전달되는 사용자정의 JpaDialect를 지원한다. 이러한 시나리오에서, DAO는 완전한 JpaTemplate 인스턴스 대신(예를 들어, JpaDaoSupport의 "jpaTemplate" 프라퍼티에 전달되는) 보다는 EntityManagerFactory 참조를 가져오지 않을것이다. JpaDialect 구현물은 언제나 업체에 종속적인 방법으로 Spring에 의해 제공되는 몇가지 고급기능이 가능하다.
특정 트랜잭션 구문 적용(사용자정의 격리 레벨이나 트랜잭션 타임아웃)
트랜잭션 성격을 가지는 JDBC Connection 가져오기(JDBC-기반의 DAO를 제시하기 위해)
Spring DataAccessExceptions 을 위한 PersistenceExceptions의 고급 번역
이것은 특별한 트랜잭션 의미와 예외의 고급 번역을 위해 부분적으로 가치가 있다. 사용된 디폴트 구현물(DefaultJpaDialect)이 어떤 특별한 기능을 제공하지 않고 위 기능이 요구된다면 적절한 dialect가 명시되어야만 한다.
이 작업의 좀더 상세한 설명과 Spring의 JPA지원내 사용되는 방법을 위해서는 JpaDialect Javadoc를 보라.