2020. 8. 16. 20:32ㆍSpring Data/MyBatis With JPA
많은 개발자들 사이에서 MyBatis VS Hibernate에 관해서 예전부터 많은 논쟁이 있어왔다. 지금까지 몇번의 프로젝트에서 두 프레임웍을 적용해서 사용 하였지만 서로간의 장단점이 있다.
어느날 꼭 한가지 프레임워크만 사용해야 할까 라는 생각이 들었다.
그래서 두 프레임워크를 동시에 적용도 해보았다.
개인적으로는 동시에 사용할때 개발 생산성이 좋았던거 같다.
예를 들면 쇼핑몰을 예를 들어보자 관리자 사이트의 경우는 JPA로 구현하여도 크게 무리가 없을 것이다. 물론 통계나 정산관련해서는 복잡한 조회 쿼리가 존재하겠지만 70-80% 정도는 단순한 CRUD성일 것이다.
이런경우 복잡한 통계,정산관련 조회 쿼리는 myBatis로 처리하면 좀더 개발자들에게는 편할 것이다.
JPA의 경우는 복잡한 집계성 쿼리의 구현이 쉽지는 않기 때문에 myBatis를 사용하는것이 낫지 않을까 싶다.
그럼 예제프로그램을 통하여서 MyBatis와 JPA를 동시에 사용하는 방법을 알아보도록하자.
프로젝트설정
1. Spring Starter Proejct 로 프로젝트 생성
2. pom.xml 작성
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.roopy</groupId>
<artifactId>springboot-mybatis-with-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot-mybatis-with-jpa</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<maven-jar-plugin.version>3.1.1</maven-jar-plugin.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- MySQL Driver -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!-- PostgerSQL Driver -->
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- DataSource -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>28.1-jre</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<!-- Apache -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
3. application.xml 작성
server:
port: 9091
spring:
main:
# SpringBoot 구동시 Bean이 중복선언되는 경우 오류가 발생하는 경우
# 중복 생성 허용 가능 하도록 하는 옵션
allow-bean-definition-overriding: true
output:
# Console Color 표시
ansi:
enabled: ALWAYS
datasource:
mysqlds:
maximum-pool-size: 4
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/roopy?allowPublicKeyRetrieval=true&useUnicode=true&useLegacyDatetimeCode=false&serverTimezone=Asia/Seoul&useSSL=false
username: root
password: admin1234
pgsqlds:
maximum-pool-size: 4
driver-class-name: org.postgresql.Driver
jdbc-url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: admin!@34
logging:
level:
org.springframework.web: INFO
org.hibernate: INFO
com.roopy: DEBUG
4. 데이터베이스설정
데이터베이스설정은 Java Config를 이용하여서 설정 하도록 한다.
1) BaseDataBaseConfig.java 이다.
이 소스는 JPA, MyBatis 공통 속성 정보를 관리하는 클래스 이다.
package com.roopy.config;
import java.io.IOException;
import javax.sql.DataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import com.google.common.collect.ImmutableMap;
/**
* Jpa, myBatis 정보 설정 클래스
*/
public class BaseDataBaseConfig {
/**
* JPA 정보 설정
*
* @param factory
* @param dbmsType
*/
protected void setConfigureEntityManagerFactory(LocalContainerEntityManagerFactoryBean factory, String dbmsType) {
String dialectName = "P".equals(dbmsType) ? "org.hibernate.dialect.PostgreSQLDialect" : "org.hibernate.dialect.MySQL5Dialect";
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
vendorAdapter.setShowSql(true);
factory.setJpaVendorAdapter(vendorAdapter);
factory.setJpaPropertyMap(ImmutableMap.of(
"hibernate.hbm2ddl.auto","none",
"hibernate.dialect", dialectName,
"hibernate.open-in-view", "true",
"hibernate.format_sql", "true"
));
factory.afterPropertiesSet();
}
/**
* MySQL 정보 설정
*
* @param sessionFactoryBean
* @param dataSource
* @throws IOException
*/
protected void setConfigureSqlSessionFactory(SqlSessionFactoryBean sessionFactoryBean, DataSource dataSource) throws IOException {
sessionFactoryBean.setDataSource(dataSource);
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sessionFactoryBean.setConfigLocation(new ClassPathResource("config/mybatis-config.xml"));
sessionFactoryBean.setMapperLocations(resolver.getResources("classpath:sqlmap/**/*.xml"));
}
}
JPA 정보 설정
- 위에 dialectName은 MySQL, PostgreSQL 두가지 DB를 연결할 것이므로 파라키터로 받는다.
MyBatis 정보설정
- myBatis 설정 정보 파일 설정
- 실제로 작성될 SQL Query문을 관리할 mapper 파일 위치 설정 및 DummyMapper.xml 작성
DummyMapper.xml 작성하지 않으면 서버 구동 시 오류가 발생하게 된다.
[resources/config/mybatis-config.xml]
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="cacheEnabled" value="true" />
<setting name="useColumnLabel" value="true" />
<setting name="useGeneratedKeys" value="false" />
<setting name="mapUnderscoreToCamelCase" value="true" />
<setting name="defaultStatementTimeout" value="25000" />
<setting name="mapUnderscoreToCamelCase" value="true" />
<setting name="jdbcTypeForNull" value="NULL"/>
</settings>
<typeAliases>
<package name="com.roopy.domain"/>
</typeAliases>
</configuration>
myBatis 설정 정보 파일
[resources/sqlmap/DummyMapper.xml]
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="sqlmap.DummyMapper">
</mapper>
2) MySQLDb.java
MySQL 데이터베이스 연결 관련 클래스
package com.roopy.config;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import com.zaxxer.hikari.HikariDataSource;
/**
* MySQL DB 설정
*/
@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef = "mysqlEntityManagerFactory",
transactionManagerRef = "mysqlTransactionManager",
basePackages = {"com.roopy.persistence.mysql.repository"}
)
@MapperScan(
sqlSessionFactoryRef = "mysqlSqlSessionFactory",
basePackages = {"com.roopy.persistence.mysql.mapper"}
)
public class MySQLDb extends BaseDataBaseConfig {
/**
* 데이터베이스 연결
* <pre>
* NOTE: 데이터베이스 설정 정보는 application.yml에 설정된
* dataSource 설정 정보이다.
* 여기 정보를 가져오는 부분이 @ConfigurationProperties
* 이다.
* </pre>
*
* @return
*/
@Primary
@Bean(name = "mysqlDataSource")
@ConfigurationProperties(prefix = "spring.datasource.mysqlds")
public DataSource dataSource2()
{
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
/**
* EntityManagerFactory 생성
*
* @param dataSource
* @return
*/
@Primary
@Bean(name = "mysqlEntityManagerFactory")
public EntityManagerFactory entityManagerFactory(@Qualifier("mysqlDataSource") DataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource);
factory.setPackagesToScan("com.roopy.domain");
factory.setPersistenceUnitName("MySQL");
setConfigureEntityManagerFactory(factory, "M");
return factory.getObject();
}
/**
* SessionFactory 생성
*
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "mysqlSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
setConfigureSqlSessionFactory(sessionFactoryBean, dataSource);
return sessionFactoryBean.getObject();
}
/**
* Transaction 설정
*
* @param entityManagerFactory
* @return
*/
@Primary
@Bean(name = "mysqlTransactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("mysqlEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
@Primary 선언을 꼭 해줘야 한다. 그렇지 않으면 Repository 연동시 아래 PgSQLDb의 EntityManager를 두개로 인식하면서 오류가 발생하게 된다.
3) PgSQLDb.java
package com.roopy.config;
import javax.persistence.EntityManagerFactory;
import javax.sql.DataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import com.zaxxer.hikari.HikariDataSource;
/**
* PostgreSQL DB 설정
*/
@Configuration
@EnableJpaRepositories(
entityManagerFactoryRef = "pgsqlEntityManagerFactory",
transactionManagerRef = "pgsqlTransactionManager",
basePackages = {"com.roopy.persistence.pgsql.repository"}
)
@MapperScan(
sqlSessionFactoryRef = "pgsqlSqlSessionFactory",
basePackages = {"com.roopy.persistence.pgsql.mapper"}
)
public class PgSQLDb extends BaseDataBaseConfig {
/**
* 데이터베이스 연결
* <pre>
* NOTE: 데이터베이스 설정 정보는 application.yml에 설정된
* dataSource 설정 정보이다.
* 여기 정보를 가져오는 부분이 @ConfigurationProperties
* 이다.
* </pre>
*
* @return
*/
@Bean(name = "pgsqlDataSource")
@ConfigurationProperties("spring.datasource.pgsqlds")
public DataSource dataSource2()
{
return DataSourceBuilder.create().type(HikariDataSource.class).build();
}
/**
* EntityManagerFactory 생성
*
* @param dataSource
* @return
*/
@Bean(name = "pgsqlEntityManagerFactory")
public EntityManagerFactory entityManagerFactory(@Qualifier("pgsqlDataSource") HikariDataSource dataSource) {
LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
factory.setDataSource(dataSource);
factory.setPackagesToScan("com.roopy.domain");
factory.setPersistenceUnitName("PostgreSQL");
setConfigureEntityManagerFactory(factory, "P");
return factory.getObject();
}
/**
* SessionFactory 생성
*
* @param dataSource
* @return
* @throws Exception
*/
@Bean(name = "pgsqlSqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("pgsqlDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
setConfigureSqlSessionFactory(sessionFactoryBean, dataSource);
return sessionFactoryBean.getObject();
}
/**
* Transaction 설정
*
* @param entityManagerFactory
* @return
*/
@Bean(name = "pgsqlTransactionManager")
public PlatformTransactionManager transactionManager(
@Qualifier("pgsqlEntityManagerFactory") EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
}
위의 데이터베이스연결정보 소스 들에서 @MapperScan에 선언된 basePackages선언된 팩키지는 꼭 만들어줘야한다.
그렇지 않으면 서버 구동시 오류가 발생한다.
5. 프로젝트구조
6. 서버 구동
위의 프로젝트구조와 같이 모든 소스 작성 및 설정 파일 작성이 끝났으면 MainApplication을 실행 하여서 서버가 정상적으로 구동 되었는지 확인한다.
프로젝트에서 시간을 가장 많이 잡는 시간이 설정인거 같다.
다른 분들의 글들을 많이 참고하였으나 북마크를 해놓지 않아서 관련 사이트를 표기 할 수 없음을 밝힙니다.
마지막으로 소스는 아래사이트에서 확인하시면 됩니다.
https://github.com/roopy1210/springboot-mybatis-with-jpa
앞으로 MySQL, PostgreSQL 에서 myBatis와 JPA를 동시에 사용하는 예제를 작성해보자.
'Spring Data > MyBatis With JPA' 카테고리의 다른 글
Spring Boot myBatis with JPA - 예제코드 (0) | 2020.08.23 |
---|