94일차 Spring 데이터연동 설정

쿠우·2022년 8월 16일
0

pom에 추가해야할 내용들이 몇개 있음 spring-jdbc , spring-tx ,mybatis-spring

		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
        

		<!-- =============== MyBatis =============== -->

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.10</version>
        </dependency>

		<dependency>
			<groupId>org.mybatis</groupId>
			<artifactId>mybatis-spring</artifactId>
			<version>2.0.7</version>
		</dependency>
		     

트랜잭션 관리자는 모두 X/Open XA표준에 따라 만들게 되어있다. (spring-tx)
(XA 표준이 뭘까) -> 참고: https://heni.tistory.com/10

-웹 뷰와 관련이 적은 데이터 영역은 root-context.xml에서 의존성 주입해준다.

-알아본 점
(1)hikariDataSource를 이용한 방법과 MyBatis DataSource를 이용한 방법 이 있다.

둘다 빈에 올리면 DataSource에 어떤것을 주입해야하는지 에러난다.
primary속성으로 기본을 정해줌 or Resource어노테이션으로 정한다.

(2)sqlSessionFactory 객체에 어떤 데이터소스에서 주입할지 정해서 주입하자
(3)mybatis의 namespaces에 추가해주기

<?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:context="http://www.springframework.org/schema/context"
	xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"
	xsi:schemaLocation="http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd
		http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd">
	
	<!-- Root Context: defines shared resources visible to all other web components -->

	<bean
		id="hikariConfig"
		class="com.zaxxer.hikari.HikariConfig">
		<description>HikariCP Configuration</description>

		<property name="driverClassName" value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>
		<property name="jdbcUrl" value="jdbc:log4jdbc:oracle:thin:@atp20191201_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP"/>
		<property name="username" value="HR"/>
		<property name="password" value="Oracle12345678"/>
		
		<property name="maximumPoolSize" value="10"/>
		<property name="minimumIdle" value="2"/>
		<property name="idleTimeout" value="10000"/>
		<property name="connectionTimeout" value="3000"/>
		<property name="connectionTestQuery" value="SELECT 1 FROM dual"/>
		<property name="dataSourceJNDI" value="jdbc/HikariCP"/>
		<property name="poolName" value="*** HikariDataSource ***"/>
	</bean>

	<bean
		primary="true"
		id="hikariDataSource"
		class="com.zaxxer.hikari.HikariDataSource"
		destroy-method="close">
		<description>HikariCP DataSource</description>

		<constructor-arg ref="hikariConfig"/>
	</bean>

	<!-- ==== MyBatis's DataSource Configuration === -->

	<bean
		primary="false"
		id="pooledDataSource"
		class="org.apache.ibatis.datasource.pooled.PooledDataSource"
		destroy-method="forceCloseAll">
		<description>MyBatis Pooled Data Source</description>

		<property name="driver" 	value="net.sf.log4jdbc.sql.jdbcapi.DriverSpy"/>
		<property name="url" 		value="jdbc:log4jdbc:oracle:thin:@atp20191201_high?TNS_ADMIN=C:/opt/OracleCloudWallet/ATP"/>
		<property name="username" 	value="HR"/>
		<property name="password" 	value="Oracle12345678"/>

		<property name="poolMaximumActiveConnections" 	value="5"/>
		<property name="poolMaximumIdleConnections" 	value="2"/>
		<property name="poolPingEnabled" 				value="true"/>
		<property name="poolPingQuery" 					value="SELECT 1 FROM dual"/>
		<property name="loginTimeout" 					value="1"/>
	</bean>

	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="hikariDataSource" />
		<!-- <property name="dataSource" ref="pooledDataSource" /> -->

		<property name="configLocation" value="classpath:mybatis-config.xml" />
		
		<!-- 위에 마이바티스 설정파일의 위치를 설정하였으면, 
			 아래의 속성은 절대-반드시-네버-에버 설정하지 말것! -->
		<!-- <property name="mapperLocations" value="classpath:mappers/**/*Mapper.xml" /> -->
	</bean>

	
	<mybatis-spring:scan base-package="org.zerock.myapp.mapper" />
	
		
</beans>

mybatis-spring library에 SqlSessionFactoryBean 이라는 이름의 클래스가 들어있다.
스프링의 BeanContainer에 Bean으로 등록 가능하도록 해주는 Bean 클래스이다.

-mapper Interface / ~~mapper.xml 준비

-mapper Interface
1. mybatis 학습시에 다 배웠던 것이다. 부족한 부분은 해당 블로그 글에서 보충

public interface EmployeesMapper {
		
	@Select("SELECT * FROM employees WHERE employee_id > 0")
	public abstract List<EmployeeVO> getAllEmployees();
	
	
	// 마이바티스는, 아래의 "자동실행규칙"을 따르는 경우, 
	// (1) Mapper Interface의 패키지와 동일한 폴더구조를 생성하라!
	// (2) 위(1)의 폴더아래에, Mapper Interface의 타입명과 동일한 이름의 Mapper XML파일을 생성하라!
	// (3) 위(2)에서 생성한 Mapper XML 파일의 namespace 속성의 값은, Mapper Interface의 FQCN으로 지정하라!!
	// (4) 위(2)에서 생성한 Mapper XML 파일에 등록할 SQL문장의 id 속성의 값은,
	//     Mapper Interface의 추상메소드 이름과 동일하게 지으라!
	// 더이상 namespace + "." + sqlId 로 사상된 SQL문장을 실행시키지 못하는 환경에서,
	// 자동으로 수행시킬 SQL문장을 결정하여 실행시킬 수 있습니다.
	public abstract List<String> getAllEmployeesNames();

} // end class

- ~~mapper.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="FirstMapper">

    <select
        id="DQL1"
        resultType="org.zerock.myapp.domain.EmployeeVO">
        SELECT *
        FROM employees
        WHERE employee_id > #{empid}
    </select>



</mapper>
<?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="SecondMapper">

    <select
        id="DQL2"
        resultType="org.zerock.myapp.domain.EmployeeVO">
        SELECT *
        FROM employees
        WHERE email LIKE #{email} AND salary > #{salary}
    </select>


    
</mapper>

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


  <mappers>
    <mapper resource="mappers/FirstMapper.xml"/>
    <mapper resource="mappers/SecondMapper.xml"/>
  </mappers>


</configuration>

test해보는 예제

(VO는 스키마 보고 알아서 하기)

  • DataSource에서는 close 메소드를 따로 가지고 있지 않다. HikariDataSource 에서 close를 가지고 있기 때문에 형변환 해주고 닫는다.
  • mybatis의 DataSource를 쓸때도 많다.
  • 둘다 빈에 올리면 DataSource에 어떤것을 주입해야하는지 에러난다. primary속성으로 기본을 정해줌
    or Resource어노테이션으로 전한다.
  • myBatis Framework 의 가장 핵심 객체는 "sql SessionFactorty"이다

-완전 기본예제1

@Log4j2
@NoArgsConstructor

// For JUnit 4
//@RunWith(SpringRunner.class)
//@RunWith(SpringJUnit4ClassRunner.class)

// For JUnit 5
@ExtendWith(SpringExtension.class)

@ContextConfiguration(locations= "file:src/main/webapp/WEB-INF/spring/root-context.xml")

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class HikariDataSourceTests {
	
//	@Resource(type=javax.sql.DataSource.class)
//	@Resource
//	@Inject
//	@Autowired

	@Setter(onMethod_= { @Resource(type=HikariDataSource.class) })
//	@Setter(onMethod_= { @Resource })
//	@Setter(onMethod_= { @Inject })	
//	@Setter(onMethod_= { @Autowired })		// @since java 8 and above
	private DataSource dataSource;
	
	
	// 1. 선처리(Pre-processing) 작업:
	//    필드에 원하는 타입의 빈(Bean)객체가 잘 주입(DI)되었는지 확인
	@BeforeAll
	void beforeAll() {
		log.trace("beforeAll() invoked.");
		
		// 필드에 의존성 객체가 잘 주입되었는지 확인
//		Objects.requireNonNull(this.dataSource);			// 1st. method
//		assert this.dataSource != null;						// 2nd. method
		assertNotNull(this.dataSource);						// 3rd. method
		
		log.info("\t+ this.dataSource: {}", this.dataSource);
	} // beforeAll
	
	
	@Test
	void dummyTest() {;;}
	
	
	@Test
	@Order(1)
	@DisplayName("1. javax.sql.DataSource.getConnection() method test.")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testGetConnection() throws SQLException {
		log.trace("testGetConnection() invoked.");
		
		// in jdbc, Connection => PreparedStatement => ResultSet : when closing, ResultSet > PreparedStatement > Connection
		// try (Connection; PreparedStatement; ResultSet;) { ... }
				
		// AutoCloseable 한 자원객체임 : 다 쓰고 나면 반드시 close(자원해제) 해줘야 함!!! (***)
		// DataSource.getConnection() 메소드 => 무한정 기다림(blocking)
		
//		@Cleanup
		Connection conn = this.dataSource.getConnection();	// Connection Pool로부터 빌린 Connection
		
		try (conn) {
			
			Objects.requireNonNull(conn);
			log.info("\t+ conn: {}, type: {}", conn, conn.getClass().getName());
			
		} // try-with-resources
	} // testGetConnection
	
}// end class

-예제2 (그냥 mybatis 부분 복습한다는 느낌)

@Log4j2
@NoArgsConstructor

// For JUnit 4
//@RunWith(SpringRunner.class)
//@RunWith(SpringJUnit4ClassRunner.class)

// For JUnit 5
@ExtendWith(SpringExtension.class)

@ContextConfiguration(locations="file:src/main/webapp/WEB-INF/spring/root-context.xml")

@TestInstance(Lifecycle.PER_CLASS)
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class SqlSessionFactoryTests {
	
	@Setter(onMethod_= {@Autowired})
	private SqlSessionFactory sqlSessionFactory;
	
	
	
	@BeforeAll
	void beforeAll() {
		log.trace("beforeAll() invoked.");
		
		assertNotNull(this.sqlSessionFactory);
		log.info("\t+ this.sqlSessionFactory: {}", this.sqlSessionFactory);
	} // beforeAll
	

//	@Disabled
	@Test
	@Order(1)
	@DisplayName("1. testFirstMapperXML")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testFirstMapperXML() {
		log.trace("testFirstMapperXML() invoked.");
		
		@Cleanup
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		// Mapped Statement 를 결정하기 위한 요소 2가지:
		String namespace = "FirstMapper";
		String sqlId = "DQL1";		
		String sql = namespace + "." + sqlId;
		
		sqlSession.<EmployeeVO>selectList(sql, 130).forEach(log::info);
	} // testFirstMapperXML
	
	
//	@Disabled
	@Test
	@Order(2)
	@DisplayName("2. testSecondMapperXML")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testSecondMapperXML() {
		log.trace("testSecondMapperXML() invoked.");
		
		@Cleanup
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		// Mapped Statement 를 결정하기 위한 요소 2가지:
		String namespace = "SecondMapper";
		String sqlId = "DQL2";		
		String sql = namespace + "." + sqlId;
		
		
		// SQL문장의 바인드변수들에 줄 값들을,
		//   (1) Map객체(key가 바인드변수명)를 만들어 주거나
		//   (2) 자바빈즈객체(property가 바인드변수명)를 만들어 줘야 한다. <------ ***
		
		@Data
		class Parameters {	// 로컬클래스(지역클래스)
			private String email;
			private Double salary;
			
		} // end class
		
		Parameters params = new Parameters();
		params.setEmail("%A%");
		params.setSalary(7000.0);
		
		sqlSession.<EmployeeVO>selectList(sql, params).forEach(log::info);
	} // testSecondMapperXML
	
	
//	@Disabled
	@Test
	@Order(3)
	@DisplayName("3. testGetAllEmployeesInEmployeesMapper")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testGetAllEmployeesInEmployeesMapper() {
		log.trace("testGetAllEmployeesInEmployeesMapper() invoked.");
		
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		try (sqlSession) {
			EmployeesMapper mapper = sqlSession.getMapper(EmployeesMapper.class);
			
			Objects.requireNonNull(mapper);
			log.info("\t+ mapper: {}, type: {}", mapper, mapper.getClass().getName());
			
			mapper.getAllEmployees().forEach(log::info);
		} // try-with-resources
	} // testGetAllEmployeesInEmployeesMapper
	
	
//	@Disabled
	@Test
	@Order(4)
	@DisplayName("4. testGetAllEmployeesNames")
	@Timeout(value=3, unit=TimeUnit.SECONDS)
	void testGetAllEmployeesNames() {
		log.trace("testGetAllEmployeesNames() invoked.");
		
		SqlSession sqlSession = this.sqlSessionFactory.openSession();
		
		try (sqlSession) {
			EmployeesMapper mapper = sqlSession.getMapper(EmployeesMapper.class);
			
			assertNotNull(mapper);
			log.info("\t+ mapper: {}, type: {}", mapper, mapper.getClass().getName());
			
			mapper.getAllEmployeesNames().forEach(log::info);
		} // try-with-resources
	} // testGetAllEmployeesNames

} // end class

수업 내용중 중요한것은 context.xml 파일 설정과 어노테이션을 통해 DataSource를 사용 하기위해 주입되는 객체를 중점으로 보면되겠다.

profile
일단 흐자

0개의 댓글