코딩 개발/Spring

MyBatis 3탄 - @Mapper, sql include, ![CDATA[]], selectKey, ||

호소세 2023. 7. 12. 21:42
728x90
반응형

MyBatis 1탄 : https://pabeba.tistory.com/196

MyBatis 2탄 : https://pabeba.tistory.com/197

 

처음부터 3탄을 보게 된다면... 이해가 안 될 수 있습니다. 조금 생략하면서 진행될 예정입니다.

이전에 했던 설정들은 그대로니 이전 2탄의 설정을 들고 와서 3탄의 공부를 진행하는 것도 좋을 것 같습니다.

 

@Mapper 어노테이션 사용하여 Sql 실행하기

<AppConfig.java>

@Configuration  
@ComponentScan("myproject")  
@MapperScan("myproject.model") // MyBatis @Mapper 명시 인터페이스를 구현하는 Proxy 클래스 자동 생성을 위한 설정 
public class AppConfig {
	@Bean 
	public DataSource dataSource() {
		BasicDataSource dataSource=new BasicDataSource();
		dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
		dataSource.setUrl("jdbc:oracle:thin:@:1521:xe");
		dataSource.setUsername("mango");
		dataSource.setPassword("apple");
		return dataSource;
	}
	@Bean 
	public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
		SqlSessionFactoryBean sqlSessionFactoryBean=new SqlSessionFactoryBean();
		sqlSessionFactoryBean.setDataSource(dataSource);
		org.apache.ibatis.session.Configuration conf=new org.apache.ibatis.session.Configuration();
		conf.setMapUnderscoreToCamelCase(true);
		sqlSessionFactoryBean.setConfiguration(conf);
		
		return sqlSessionFactoryBean.getObject();
	}
	@Bean  
	public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
		return new SqlSessionTemplate(sqlSessionFactory);
	}
}

이전의 설정과 달라진 점

1. Sql문이 작성되어 있는 Mapper의 경로로 Resource 객체를 생성하지 않아도 되는 점

2. 맨 위에 @MapperScan 어노테이션이 생긴 점

 

1번과 같이 설정을 하게 되면 Mapper Interface 와 Mapper xml 파일을 동일한 경로(디렉터리)에 두어야 합니다.

Mapper Proxy를 이용하기 때문에 더 이상 도움을 주는 Impl 파일들이 필요 없어집니다.

 

@Mapper 사용법

<AccountMapper.java>

@Mapper 
public interface AccountMapper {
	AccountVO findAccountByNo(long accountNo);

	List<AccountVO> findAccountListOrderByNoDesc();	
}

AccountMapper 인터페이스를 생성하고 @Mapper 어노테이션을 작성해주면 현 인터페이스의 Proxy를 자동 생성해 줍니다.

 

그러니까 이 Interface 만 있어도 Sql 문이 작동된다는 말입니다. Proxy 객체가 알아서 Mapper.xml의 내용을 읽어다가 sql문을 실행하고 결과 값을 가져다줍니다.

 

<AccountMapper.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="myproject.model.AccountMapper">
	<sql id="selectAccount">
	select account_no,name,balance from spring_account
	</sql>
	<select id="findAccountByNo" resultType="myproject.model.AccountVO" parameterType="long">
	<include refid="selectAccount"></include>
	where account_no=#{value}
	</select>
	<select id="findAccountListOrderByNoDesc"  resultType="myproject.model.AccountVO">
	<include refid="selectAccount"></include>
	order by account_no desc
	</select>
</mapper>

Mapper java 파일의 메서드들에 대한 Sql 문을 작성했습니다.

id - 메서드 명

resultType - 결과로 나올 값의 타입

parameterType - 변수로 들어갈 값의 타입 (string, int, hashmap... 등등 웬만한 것을 다 들어갈 수 있습니다.)

 

hashmap으로 값 넣기 예제

SQL문의 PreparedStatement의 '? 값'이 하나만 있으면 #{value}로 작성되는데, 만약에 여러 개의 값을 넣어줘야 하는 상황이 생기면 hashmap이나 VO객체를 이용해서 넣어줘야 합니다.

<insert id="register" parameterType="hashmap">
insert into spring_member(id,password,name,address) values(#{ID},#{PASS},#{NAME},#{ADDR})
</insert>

이런 식으로 말이죠.

 

중복되는 SQL 문 include 사용하기

여기서 배울 것은 <sql> 태그입니다.

 

개발자들은 항상 중복에 대한 경계가 있기 때문에 중복되는 Sql 문인

select account_no,name,balance from spring_account

이 문장을 selectAccount라는 id 값에 넣어 저장하고

각각의 메서드에 맞는 sql 문장을 생성할 때

<include> 태그를 이용하여 중복 값을 없앴습니다.

<select id="findAccountByNo" resultType="myproject.model.AccountVO" parameterType="long">
<include refid="selectAccount"></include>
where account_no=#{value}
</select>

이렇게 말이죠.

 

<TestAccountList.java>

public class TestAccountList {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
		System.out.println(ctx.getBean("accountMapper"));
		AccountMapper am=(AccountMapper)ctx.getBean("accountMapper");
		List<AccountVO> list=am.findAccountListOrderByNoDesc();
		for(AccountVO vo:list)
			System.out.println(vo);
		ctx.close();
	}
}

오랜만에 Test 코드를 쫌 파헤쳐 볼까요?

1. AnnotationConfigApplicationContext를 이용하여 Configuration 파일을 읽어오라고 명령합니다.

2. accountMapper Bean 이 Proxy 인지 확인해 봅니다.

3. AccountMapper 프락시를 이용하여 함수 실행하면  자동으로 SQL 문이 실행되면서 결과 값을 가져옵니다.

4. 값을 확인해 봅니다.

org.apache.ibatis.binding.MapperProxy@437 ebf59

2023-07-12 21:06:29 DEBUG o.k.m.m.A.findAccountListOrderByNoDesc - ==> Preparing: select account_no, name, balance from spring_account order by account_no desc

2023-07-12 21:06:29 DEBUG o.k.m.m.A.findAccountListOrderByNoDesc - ==> Parameters:

2023-07-12 21:06:29 DEBUG o.k.m.m.A.findAccountListOrderByNoDesc - <== Total: 5

AccountVO [accountNo=21, name=아이유, balance=700]

AccountVO [accountNo=4, name=아이유, balance=700]

AccountVO [accountNo=3, name=황희찬, balance=200]

AccountVO [accountNo=2, name=이강인, balance=300]

AccountVO [accountNo=1, name=손흥민, balance=100]

값이 잘 나오는군요.

 

! [CDATA []]

CDATA는 Character Data의 약어로 XML 문서에서 텍스트 데이터를 표시하는 방법 중 하나입니다.

텍스트의 무결성을 보존하기 위해 사용됩니다. 

이것이 사용되는 이유 중 하나를 보여드리겠습니다.

 

가격이 300원 미만인 물건을 찾는 SQL 문이 있다고 생각해 봅시다.

Mapper.xml 파일에 작성한다고 가정하겠습니다.

<sql id="selectProduct">
	SELECT product_no,name,maker,price FROM spring_product
</sql>
<select id="findProductListByMakerAndPrice" parameterType="myproject.model.ProductVO" 
	 resultType="myproject.model.ProductVO">
	 <include refid="selectProduct"></include>	 
	 WHERE maker=#{maker} AND price<#{price}
</select>

이렇게 작성하면 바로 에러가 납니다.

왜냐하면 태그를 둘러싸고 있는 '<'이 있기 때문에 xml에서는 태그가 시작되는 줄 알고 있습니다.

 

이러한 문제 때문에! [CDATA []] 를 사용하는 것입니다.

 

<select id="findProductListByMakerAndPrice" parameterType="myproject.model.ProductVO" 
	 resultType="myproject.model.ProductVO">
	 <include refid="selectProduct"></include>	 
	 <![CDATA[
	 WHERE maker=#{maker} AND price<#{price}
	 ]]>
</select>

이런 식으로 변경하면 아무 문제 없이 잘 사용할 수 있습니다.

 

<selectKey>를 이용하여 현재 생성한 데이터의 sequence 번호 알아보기

insert 문장으로 데이터를 넣을 때 보통 id 값을 숫자로 하게 되면 sequence 키로 순차증가하게 만들지 않습니까?

그런데 자신이 저장한 id의 값을 inset 할 때는 모르고 다시 검색해 봐야 알 수 있는데, 

<selectKey>를 사용하면 바로 알아볼 수 있다는 겁니다.

<insert id="registerVer2" parameterType="myproject.model.ProductVO">
	INSERT INTO spring_product VALUES(spring_product_seq.nextval,#{name},#{maker},#{price})
	<selectKey keyColumn="product_no" keyProperty="productNo" order="AFTER" resultType="long">
		SELECT spring_product_seq.currval from dual
	</selectKey>
</insert>

selectKey를 분석해 보겠습니다.

keyColumn - DB에 저장된 이름 작성

keyProperty - java VO에  저장된 변수 값

order - sql이 실행되고 값을 받을 것인지(AFTER), 실행 전에 받을 것인지(BEFORE)

resultType - 결과 값 타입

 

을 넣어주고 저희는 제품의 현재 sequence 숫자를 가져올 것이기 때문에 dual 테이블에서 값을 가져옵니다.

 

Test code를 보겠습니다.

Test code

public class TestInsertProductPRGPattern {
	public static void main(String[] args) {
		AnnotationConfigApplicationContext ctx=new AnnotationConfigApplicationContext(AppConfig.class);
		ProductMapper productMapper=(ProductMapper)ctx.getBean("productMapper");
		ProductVO productVO=new ProductVO();
		productVO.setName("감자깡");
		productVO.setMaker("농심");
		productVO.setPrice(17000);
		System.out.println("상품등록완료전:"+productVO);
		productMapper.registerVer2(productVO);
		System.out.println("상품등록완료후:"+productVO);
		System.out.println("등록된 상품정보:"
		+productMapper.findProductByNo(productVO.getProductNo()));	
		ctx.close();
	}
}

상품등록완료 전:ProductVO [productNo=0, name=감자깡, maker=농심, price=17000]

상품등록완료 후:ProductVO [productNo=10, name=감자깡, maker=농심, price=17000]

등록 전에는 productNo를 모르기 때문에 default 값인 0이 나왔지만

등록 후에는 selectkey 덕분에 번호를 자동으로 가져올 수 있게 됩니다.

 

' || '를 이용하여 % LIKE 문장 사용

Java에서는 OR의 의미로 많이 사용되는 '||'  기호입니다.

SQL에서는 중간에 문자를 합쳐주기 위한 용도로 사용됩니다.

 

예시

<select id="findProductListLikeKeyword" parameterType="string" resultType="myproject.model.ProductVO">
	 <include refid="selectProduct"></include>
	WHERE name LIKE '%' || #{value} || '%'
</select>

원래 문장이

WHERE name LIKE '%검색어%'

이것인데 ' ' 안에 또 다른 문장이 들어가야 하는 상황이기 때문에 '||'을 사용하여 넣은 것입니다.

 

소감 

이것 말고도 다른 문법들이 많겠지만 일단 오늘은 여기까지 알아보도록 할게요.

날이 더워서 사람들이 에어컨을 많이 틀거나 선풍기 바람을 많이 맞는지 감기에 많이 걸리더라고요. 저도 가벼운 목감기에 걸렸는데, 모두 이런 날에 더 건강을 챙겨야 합니다. 여름 감기가 진짜 오래가는 것 같아요.

조금 쳐지는 날이지만 좋은 생각을 하면서 내일은 어떤 재미난 일이 생길까, 너무 먼 미래 같다면 1시간 뒤에는 무슨 재미난 일이 생길까라는 흥미로운 생각을 하면서 살아가보도록 해요. 마음먹기에 따라 삶이 변한다고 하니까요.

반응형