[Servlet][MyBatis] Mapper 설정 #2 typeHandlers, Handling Enums, objectFactory, plugins, mappers

2022. 2. 23. 18:06JAVA/MyBatis

<!-- 매퍼 인터페이스를 사용 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>​
학습목표
1. typeHandlers를 설정하고 생성하는 방법에 대해서 학습
2. enumTypeHandler와 enumOrdinalTypeHandler를 설정하고 생성하는 방법에 대해서 학습
3. objectFactory를 생성하고 설정하는 방법에 대해서 학습
4. plugins 인터페이스를 구현하고 설정하는 방법에 대해서 학습

 

1. typeHandlers

typeHandlers의 역할은 무엇인가?

typeHandler의 역할은 mybatis가 PreparedStatement에 파라미터를 설정하고 ResultSet에서 값을 가져올 때마다 적절한 자바 타입으로 변환해주는 역할을 수행합니다. 아래의 링크는 디폴트 TypeHandlers의 표입니다.

https://mybatis.org/mybatis-3/ko/configuration.html#typeHandlers

 

MyBatis – 마이바티스 3 | 매퍼 설정

매퍼 설정 마이바티스 XML 설정파일은 다양한 설정과 프로퍼티를 가진다. 문서의 구조는 다음과 같다.: configuration properties 이 설정은 외부에 옮길 수 있다. 자바 프로퍼티 파일 인스턴스에 설정할

mybatis.org

 

typeHandler 생성

만약 지원하지 않거나 비표준인 타입에 대해서는 TypeHandler 인터페이스를 구현하고 자바 타입에 직접 생성한 TypeHandler를 매핑할 수 있습니다.

 

다음 예제는 enum 타입의 핸들러를 생성하는 예제입니다.

 

1. 사용할 enum 타입 생성

public enum LibRegiNumType {
	충남대학교(1),
	목원대학교(2),
	우송대학교(3),
	배재대학교(4),
	한남대학교(5);	

	private int value;
	LibRegiNumType(int value) {
		this.value = value;
	}
	public int getValue() {
		return value;
	}
	
	public static LibRegiNumType lookup(int num) {
		for(LibRegiNumType element : LibRegiNumType.values())
		{
			if(element.getValue()==num)
			{
				return element;
			}
		}
		return null;
	}
	
}

 

2. LibRegiNumTypeHandler 생성

@MappedJdbcTypes(JdbcType.INTEGER)
@MappedTypes(LibRegiNumType.class)
public class LibRegiNumTypeHandler extends BaseTypeHandler<LibRegiNumType>{

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, LibRegiNumType parameter, JdbcType jdbcType)
			throws SQLException {
		System.out.println("call setNonNullParameter");
		ps.setInt(i, parameter.getValue());
	}
	
	@Override
	public LibRegiNumType getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		System.out.println("call NullableResult cs");
		LibRegiNumType element = LibRegiNumType.lookup(cs.getInt(columnIndex));
		System.out.println(element);
		return element;
	}

	@Override
	public LibRegiNumType getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		System.out.println("call NullableResult rs columnIndex");
		LibRegiNumType element = LibRegiNumType.lookup(rs.getInt(columnIndex));
		System.out.println(element);
		return element;
	}

	@Override
	public LibRegiNumType getNullableResult(ResultSet rs, String columnName) throws SQLException {
		System.out.println("call NullableResult rs columnName");
		LibRegiNumType element = LibRegiNumType.lookup(rs.getInt(columnName));
		System.out.println(element);
		return element;
	}
}

 

3. mybatis-config.xml 설정파일에 핸들러 설정

	<typeHandlers>
		<!-- Enum 타입 핸들러 -->
  		<typeHandler handler="mybatis.typehandler.LibRegiNumTypeHandler"/>
				
  		<typeHandler javaType="String"
  					jdbcType="VARCHAR"
  					handler="mybatis.typehandler.ExampleTypeHandler"/> <!-- typehandler 설정방법1 -->
  		<!-- <package name="mybatis.typehandler"/> -->	<!-- typehandler 설정방법2 -->	  
	</typeHandlers>

 

4. 쿼리문 작성 (MemberMapper.xml)

	<resultMap type="member.model.vo.Member2" id="membermap">
		<id column="mem_num" property="mem_num"/>
		<result column="mem_name" property="mem_name"/>
		<result column="mem_email" property="mem_email"/>
		<result column="lib_regi_num" property="lib_regi_num"/>
	</resultMap>
	<select id="findByNum" parameterType="mybatis.typehandler.LibRegiNumTypeHandler" resultMap="membermap">
		select * from member where mem_num = #{mem_num}
	</select>

 

5. 도메인 클래스 작성 (Member2)

public class Member2 {
	private int mem_num;
	private String pwd;
	private String mem_email;
	private String mem_name;
	private LibRegiNumType lib_regi_num;
	private int loan_num;
	private int rsr_num;
	private Date due_date;
	private Date mtl_loan_date;

	public Member2() {
	}

...
}

 

6. db의 member 테이블에서 lib_regi_num 컬럼 확인

아래의 그림을 보면 lib_regi_num이 db에는 숫자로 저장되어 있는 것을 볼 수 있습니다. 이제 findByNum 쿼리문을 통해서 lib_regi_num 컬럼 값을 가져올 때는 enumTypeHandler가 변환을 하여 숫자에 맞는 대학교 도서관을 가지는 enum Type을 저장할 것입니다.

 

7. findByNum 실행

매개변수로 LibRegiType을 확인할 수 있습니다.

	@Test
	public void handlingEnumTest() {
		try(SqlSession sqlSession = sqlSessionFactory.openSession()){
			Member2 member = sqlSession.selectOne("member.model.dao.MemberMapper.findByNum",LibRegiNumType.한남대학교);
			System.out.println(member);
		}
	}
Member [mem_num=5, pwd=pass1, mem_email=hang0602@gmail.com, mem_name=황현희, lib_regi_num=한남대학교, loan_num=3, rsr_num=3, due_date=null, mtl_loan_date=null]

실행결과 한 학생의 lib_regi_num이 숫자로 표현되는 것이 아닌 '한남대학교'로 출력되는 것을 볼 수 있습니다. 이는 db에서 학생의 정보를 가져올때 LibRegiTypeHandler가 변환해서 가져와 저장한 것이기 때문입니다.

 

2. Handling Enums

Enum을 매핑하고자 한다면 위의 예제에서처럼 핸들러 클래스를 생성하거나 EnumOrdinalTypeHandler를 사용할 필요가 필요가 있습니다.

 

EnumOrdinalTypeHandler 설정

<typeHandlers>
	<typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="member.model.vo.LibRegiNumType2"/>		
</typeHandlers>

 

위의 예제에서 LibRegiNumType2 enum 타입은 자동으로 EnumOrdinalTypeHandler에 의해 변환될 것입니다. 하지만 만약 명시적으로 다른 핸들러를 설정하고 싶다면 아래와 같이 설정할 수 있습니다.

	<resultMap type="member.model.vo.Member3" id="membermap3">
		<id column="mem_num" property="mem_num"/>
		<result column="mem_name" property="mem_name"/>
		<result column="mem_email" property="mem_email"/>
		<result column="lib_regi_num" property="lib_regi_num" typeHandler="mybatis.typehandler.LibRegiNumTypeHandler2"/>
	</resultMap>
	<select id="findByNum3" resultMap="membermap3">
		select * from member where mem_num = #{mem_num}
	</select>
	<insert id="insert3" parameterType="member.model.vo.Member3">
		insert into member (mem_num, pwd, mem_email, mem_name, lib_regi_num)
		values (mem_num_seq.nextval, #{pwd}, #{mem_email}, #{mem_name}, #{lib_regi_num, typeHandler=mybatis.typehandler.LibRegiNumTypeHandler2})
	</insert>

 

3. objectFactory

매번 mybatis는 결과 객체의 인스턴스를 만들기 위해 objectFactory를 사용합니다. 디폴트 ObjectFactory는 디폴트 생성자를 가진 대상 클래스를 인스턴스화하는 것보다 조금더 많은 작업을 합니다. ObjectFactory의 디폴트 행위를 오버라이드 하고자 한다면 다음과 같이 만들수 있습니다.

public class MemberObjectFactory extends DefaultObjectFactory {

	@Override
	public <T> T create(Class<T> type) {
		System.out.println("call create");
		return super.create(type);
	}
	
	@Override
	public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {
		return super.create(type, constructorArgTypes, constructorArgs);
	}

	@Override
	public void setProperties(Properties properties) {
		super.setProperties(properties);
	}	

	@Override
	public <T> boolean isCollection(Class<T> type) {
		return super.isCollection(type);
	}	
}
	<objectFactory type="mybatis.objectfactory.MemberObjectFactory">
  		<property name="someProperty" value="100"/>
	</objectFactory>
	@Test
	@Disabled
	public void objectFactoryTest() {
		MemberObjectFactory factory = new MemberObjectFactory();
		Member member = factory.create(Member.class);	// default Member 타입 객체 생성
		System.out.println(member);

		
		List<Class<?>> typeList = new ArrayList<Class<?>>();
		typeList.add(int.class);
		typeList.add(String.class);
		typeList.add(String.class);
		typeList.add(String.class);
		typeList.add(int.class);
		typeList.add(int.class);
		typeList.add(int.class);
		typeList.add(Date.class);
		typeList.add(Date.class);
		
		List<Object> argsList = new ArrayList<Object>();
		argsList.add(-1);
		argsList.add("password");
		argsList.add("user14@gmail.com");
		argsList.add("홍길동");
		argsList.add(1);
		argsList.add(0);
		argsList.add(0);
		argsList.add(null);
		argsList.add(null);
		
		Member member2 = factory.create(Member.class, typeList, argsList);
		System.out.println(member2);
		
		System.out.println(factory.isCollection(List.class));	// true

	}

실행결과

Member [mem_num=0, pwd=null, mem_email=null, mem_name=null, lib_regi_num=0, loan_num=0, rsr_num=0, due_date=null, mtl_loan_date=null]
Member [mem_num=-1, pwd=password, mem_email=user14@gmail.com, mem_name=홍길동, lib_regi_num=1, loan_num=0, rsr_num=0, due_date=null, mtl_loan_date=null]
true

 

 

4. plugins

mybatis는 매핑 구문을 실행하는 어떤 시점에 호출을 가로챌수 있습니다. 기본적으로 mybatis는 메서드 호출을 가로채기 위한 플러그인을 허용하기 때문입니다.

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

위와 같은 쿼리시 플러그인을 사용하여 가로챌 수 있습니다.

@Intercepts({ @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }) })
public class ExamplePlugin implements Interceptor {
	private Properties properties = new Properties();

	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// implement pre processing if need
		Object returnObject = invocation.proceed();
		
		System.out.println("call intercept : " + returnObject);
		// implement post processing if need
		return returnObject;
	}

	@Override
	public void setProperties(Properties properties) {
		this.properties = properties;
	}
	
	
}
	<plugins>
	  <plugin interceptor="mybatis.plugin.ExamplePlugin">
	    <property name="someProperty" value="100"/>
	  </plugin>
	</plugins>

 

	@Test
	public void pluginsTest() {
		Member member = new Member.Builder(165)
									.build();
		try(SqlSession sqlSession = sqlSessionFactory.openSession()){
			sqlSession.update("member.model.dao.MemberMapper.update",member);
			sqlSession.commit();
		}
	}
call intercept : 1

 

5. mappers

매퍼 설정은 다음과 같이 설정할 수 있습니다.

<!-- 클래스패스의 상대경로의 리소스 사용 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 절대경로의 url을 사용 -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 매퍼로 패키지내 모든 인터페이스를 등록 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

 

References

source code : https://github.com/yonghwankim-dev/mybatis_study
mybatis3 : https://mybatis.org/mybatis-3/ko/configuration.html#typeHandlers