[백업][가리사니] spring 서로 다른 종류의 db에 @transactional [예제포함]
java, postgresql, solr, spring

이 문서는 가리사니 개발자 포럼에 올렸던 글의 백업 파일입니다. 오래된 문서가 많아 현재 상황과 맞지 않을 수 있습니다.

서론

Spring @Transactional Method 적용범위 : rollback 주의

스프링 부트 1.4.x 부터는 솔라 설정법이 조금 달라집니다.

참고 : /2016/08/04/%EB%B0%B1%EC%97%85-%EA%B0%80%EB%A6%AC%EC%82%AC%EB%8B%88-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B6%80%ED%8A%B8-1.4.x-%EC%86%94%EB%9D%BC-(spring-data-solr)-%EC%97%B0%EB%8F%99-%EB%B3%80%EA%B2%BD%EC%82%AC%ED%95%AD/html

결론

한쪽에서 오류시 나머지 한쪽도 정상적으로 롤백됩니다.!! 갑자기 왜 결론부터 말씀드리냐면.. 이번 예제 소스는 조금 깁니다… (중간에 삽질해서 실험하는데 오래걸렸네요.. 벌써 새벽…. 오늘도 코딩하다.. 이력서 넣는 시간을 놓혀 버렸..;;;)

메이븐 추가

<!-- 히카리 CP -->
<dependency>
	<groupId>com.zaxxer</groupId>
	<artifactId>HikariCP</artifactId>
</dependency>
<!-- PG-SQL -->
<dependency>
	<groupId>org.postgresql</groupId>
	<artifactId>postgresql</artifactId>
</dependency>
<!-- 롬복 -->
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
	<version>1.16.8</version>
	<scope>provided</scope>
</dependency>
<!-- 하이버네이트 -->
<dependency>
	<groupId>org.hibernate</groupId>
	<artifactId>hibernate-jpamodelgen</artifactId>
	<version>5.2.1.Final</version>
</dependency>
<!-- 솔라 -->
<dependency>
	<groupId>org.springframework.data</groupId>
	<artifactId>spring-data-solr</artifactId>
	<version>1.5.4.RELEASE</version>
</dependency>
<!-- JPA -->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

연결설정

<프로젝트>/src/main/resources/database.properties ``` java # 히카리 - PostgreSQL hikari.gs.jdbcUrl: jdbc:postgresql://호스트:포트/데이터베이스?charSet=UTF-8&prepareThreshold=1 hikari.gs.username: 계정 hikari.gs.password: 암호 hikari.gs.maximumPoolSize: 10 # 솔라 solr.gs.host: http://호스트:포트/solr ``` 테이터베이스 연결설정 ``` java /** * 데이터베이스 설정
* 2016-07-16 박용서 작성 : */ public class DatabaseConfiguration { /** * 가리사니 (PostgreSQL) 히카리 데이터소스 */ @Configuration @ConfigurationProperties(prefix = "hikari.gs", locations="classpath:database.properties") public static class GsHikariDataSource extends HikariConfig { @Bean(name = "GsHikariDataSource") public DataSource getDataSource() { System.out.println("데이터 소스 생성"); return new HikariDataSource(this); } } /** * 가리사니 (PostgreSQL) 하이버네이트 */ @Configuration @EnableJpaRepositories ( basePackages = "db.hn.gs.repositories", entityManagerFactoryRef = "GsHibernateFactoryBean", transactionManagerRef = "GsHibernateTransactionManager" ) public static class GsHibernate { @Autowired @Qualifier("GsHikariDataSource") DataSource dataSource; @Bean(name = "GsHibernateFactoryBean") public LocalContainerEntityManagerFactoryBean getFactoryBean(EntityManagerFactoryBuilder builder) { return builder.dataSource(dataSource).packages("db.hn.gs.entities").build(); } @Bean(name = "GsHibernateTransactionManager") public PlatformTransactionManager getTransactionManager(EntityManagerFactoryBuilder builder) { return new JpaTransactionManager(getFactoryBean(builder).getObject()); } } @Configuration @EnableSolrRepositories ( basePackages="db.solr.gs.repositories", solrServerRef="GsSolr", solrTemplateRef="GsSolrTemplate", multicoreSupport=true ) @PropertySource("classpath:database.properties") public static class GsSolrConfig { @Value("${solr.gs.host}") String host; @Bean(name = "GsSolr") public SolrServer getSolrServer() { return new HttpSolrServer(host); } @Bean(name = "GsSolrTemplate") public SolrTemplate getSolrTemplate() { return new SolrTemplate(getSolrServer()); } } } ``` 서비스 컨포넌트를 스캔 ``` java @Configuration @ComponentScan(basePackages="db.service") public class SerivceScan { // 단순히 스캔시킬려고 만들어둔것으로 적당한 위치가 떠오르지 않아 일단 따로 빼둠. } ``` # 테이터베이스 기본테이블 PostgreSQL ``` sql CREATE TABLE users ( no bigint NOT NULL, name character varying(64) NOT NULL ); CREATE SEQUENCE users_no_seq; ``` 솔라 - managed-schema - 이 실험에선 한글 형태소를 쓸 필요가 없음으로 기본 text 자료형이 있는상태에서 사용하셔도됩니다. - 한글형태소 : http://cafe.naver.com/korlucene 에서 다운로드 - 스키마 이름은 gs_test ``` java no name ``` # JPA 엔티티 ``` java package db.solr.gs.entities; import org.apache.solr.client.solrj.beans.Field; import org.springframework.data.annotation.Id; import org.springframework.data.solr.core.mapping.SolrDocument; import lombok.Data; import lombok.Getter; import lombok.Setter; @Data @SolrDocument(solrCoreName = "gs_test") @Getter @Setter public class GsTest { @Id @Field private long no; @Field private String name; public String toString() { return no + " : " + name; } } ``` ``` java package db.hn.gs.entities; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import lombok.Data; import lombok.Getter; import lombok.Setter; @Entity @Table(name="users") @Data @Getter @Setter public class User { @Id @SequenceGenerator(name="seq", sequenceName="users_user_seq", allocationSize=1) @GeneratedValue(strategy=GenerationType.SEQUENCE, generator="seq") long no; @Column String name; public String toString() { return no + " : " + name; } } ``` # JPA 리포지토리 ``` java package db.solr.gs.repositories; import org.springframework.data.domain.Page; import org.springframework.data.solr.repository.Query; import org.springframework.data.solr.repository.SolrCrudRepository; import db.solr.gs.entities.GsTest; public interface GsTestRepository extends SolrCrudRepository<GsTest, String> { @Query(value = "*:*", filters = { "" }) Page findAll(); } ``` ``` java package db.hn.gs.repositories; import org.springframework.data.jpa.repository.JpaRepository; import db.hn.gs.entities.User; public interface UsersRepository extends JpaRepository<User, Long> { } ``` # 서비스 - 만들고나서 든 생각이지만.. 구조적으로 한단계를 더 거치는게 맞지 않을까 생각됩니다..... ``` java package db.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import db.hn.gs.entities.User; import db.hn.gs.repositories.UsersRepository; import db.solr.gs.entities.GsTest; import db.solr.gs.repositories.GsTestRepository; @Component public class UserTestService { @Autowired UsersRepository usersRepository; @Autowired GsTestRepository gsTestRepository; @Transactional public void addUser(String name) { User user = new User(); user.setName(name); User saveUser = usersRepository.save(user); GsTest test = new GsTest(); test.setNo(saveUser.getNo()); test.setName(saveUser.getName()); gsTestRepository.save(test); } @Transactional(readOnly=true) public void allPrint() { System.out.println("pg-sql users"); usersRepository.findAll().forEach(System.out::println); System.out.println("solr users"); gsTestRepository.findAll().forEach(System.out::println); } } ``` # 실행 ``` java @SpringBootApplication public class App implements CommandLineRunner { Logger logger = LoggerFactory.getLogger(App.class); @Autowired UserTestService userTestService; @Override public void run(String... args) throws Exception { logger.info("유저입력!!"); userTestService.addUser("가리사니"); userTestService.addUser("개발자공간"); logger.info("출력!!"); userTestService.allPrint(); } public static void main(String[] args) { SpringApplication.run(App.class, args); } } ``` 결과 날짜 INFO ~~ --- [ main] saro.web.App : 유저입력!! 날짜 INFO ~~ --- [ main] saro.web.App : 출력!! pg-sql users 1 : 가리사니 2 : 개발자공간 solr users 1 : 가리사니 2 : 개발자공간 # 고의로 오류를 내보자!! UserTestService 클래스 ``` java @Transactional public void addUser(String name) { User user = new User(); user.setName(name); User saveUser = usersRepository.save(user); GsTest test = new GsTest(); test.setNo(saveUser.getNo()); test.setName(saveUser.getName()); gsTestRepository.save(test); // 오류내기!! int error = 0 / 0; } ``` 결과 PostgreSQL 특성상 SEQUENCE 는 롤백되지 않기 때문에 SEQUENCE 만 1 증가한 상태이며, 나머지 양쪽 모두 롤백되었습니다.