Mybatis --- 自定义类型转换

Mybatis — 自定义类型转换

前言

在进行数据库增删改查的时候,因为实体类类型数据库类型不兼容,或者说Mybatis自带的类型转换不够用了,这时候就要自己手动操作了,最常见的就是素组的的操作,根据原有的进行扩展,这样就不用定义一个数组属性,就写一次转换方法。

接口

原生接口org.apache.ibatis.type.TypeHandler<T>

public interface TypeHandler<T> {
  /**
  用来设置查询参数
  */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;
  /**
  用来将查询结果转换成指定实体类,依旧是返回结果的列名
  */
  T getResult(ResultSet rs, String columnName) throws SQLException;
  /**
  用来将查询结果转换成指定实体类,依旧是返回结果列的顺序号
  */
  T getResult(ResultSet rs, int columnIndex) throws SQLException;
  /**
  用来将查询结果转换成指定实体类,依旧是返回结果列的顺序号,
  只是使用了不同的返回接收接口,写过原生jdbc的应该有印象
  */
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

也可以使用继承的方式扩展,我这里是直接继承 org.apache.ibatis.type.TypeReference<T>,这个可以省很多事情

public abstract class BaseTypeHandler<T> extends TypeReference<T> implements TypeHandler<T> {

  protected Configuration configuration;

  public void setConfiguration(Configuration c) {
    this.configuration = c;
  }

  public void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException {
    if (parameter == null) {
      if (jdbcType == null) {
        throw new TypeException("JDBC requires that the JdbcType must be specified for all nullable parameters.");
      }
      try {
        ps.setNull(i, jdbcType.TYPE_CODE);
      } catch (SQLException e) {
        throw new TypeException("Error setting null for parameter #" + i + " with JdbcType " + jdbcType + " . " +
        		"Try setting a different JdbcType for this parameter or a different jdbcTypeForNull configuration property. " +
        		"Cause: " + e, e);
      }
    } else {
      setNonNullParameter(ps, i, parameter, jdbcType);
    }
  }

  public T getResult(ResultSet rs, String columnName) throws SQLException {
    T result = getNullableResult(rs, columnName);
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public T getResult(ResultSet rs, int columnIndex) throws SQLException {
    T result = getNullableResult(rs, columnIndex);
    if (rs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public T getResult(CallableStatement cs, int columnIndex) throws SQLException {
    T result = getNullableResult(cs, columnIndex);
    if (cs.wasNull()) {
      return null;
    } else {
      return result;
    }
  }

  public abstract void setNonNullParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, String columnName) throws SQLException;

  public abstract T getNullableResult(ResultSet rs, int columnIndex) throws SQLException;

  public abstract T getNullableResult(CallableStatement cs, int columnIndex) throws SQLException;

}

可以看到已经帮我们把结果或者查询参数为空的情况处理好了,我们只需要把 不为空的情况补充完整即可。

代码实现

我这里是实现了一个将'1,2,3,4' 与 ['1','2','3','4']之间的转换

//这个注解定义的是JdbcType类型,这里的类型不可自己随意定义,必须要是枚举类org.apache.ibatis.type.JdbcType所枚举的数据类型
@MappedJdbcTypes({JdbcType.VARCHAR})
//这里定义的是JavaType的数据类型,描述了哪些Java类型可被拦截
@MappedTypes({List.class})
public class StringListTypeHandler extends BaseTypeHandler<List<String>> {
	private final static Logger logger = Logger.getLogger(StringListTypeHandler.class);

	public StringListTypeHandler(){
		logger.info("initialization StringListTypeHandler ok");
		System.out.println("initialization StringListTypeHandler ok");
	}

	@Override
	public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException {
		//这个我使用的是 commons.lang3 的包,用来添加 ',' 也可以直接 toString() 后去掉两端的 [] 即可
		ps.setString(i, StringUtils.join(parameter,","));
	}

	@Override
	public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException {
		String str = rs.getString(columnName);
		if (rs.wasNull())
			return null;
		//解决增加异常,这样做是因为直接放入时操作的是原素组,导致不能增删操作,所以要重新创建一个
		return new ArrayList<String>(Arrays.asList(str.split(",")));
	}

	@Override
	public List<String> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
		String str = rs.getString(columnIndex);
		if (rs.wasNull())
			return null;

		return new ArrayList<String>(Arrays.asList(str.split(",")));
	}

	@Override
	public List<String> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
		String str = cs.getString(columnIndex);
		if (cs.wasNull())
			return null;

		return new ArrayList<String>(Arrays.asList(str.split(",")));
	}
}

这样一来,工具就完成了。需要注意的是,如果没有添加 MappedJdbcTypes MappedTypes的话,要在对应的位置声明相应的类型

配置

先说下我使用的是添加了 MappedJdbcTypes MappedTypes注解,所以在spring或者mybatis配置文件里不做处理,即不额外增加配置也是可以生效的,至少我使用的版本是生效的 mybatis-3.2.3.jar mybatis-spring-1.3.1.jar,我猜测,要是没有配置注解的话或许不会生效。都配置上也不会报错,如果报错了,就得优先查看这个,比如没有找到或者加载类失败

在没有mybatis配置文件的情况下,直接配置在spring或者其他情况下,把类手动初始化,然后配置到SqlSessionFactory

	<bean id="intArrayTypeHandler" class="com.ssm.common.model.utils.IntArrayTypeHandler"/>
    <bean id="stringArrayTypeHandler" class="com.ssm.common.model.utils.StringArrayTypeHandler"/>
    <bean id="stringListTypeHandler" class="com.ssm.common.model.utils.StringListTypeHandler"/>

	<!-- 配置 SqlSessionFactory -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<!--typeHandler注入-->
		<!-- 这里是要配置多个的情况下,如果单个的话直接在 property 标签里 ref 即可 -->
        <property name="typeHandlers">
        	<list>
        		<ref bean="intArrayTypeHandler"/>
        		<ref bean="stringArrayTypeHandler"/>
        		<ref bean="stringListTypeHandler"/>
        	</list>
        </property>
	</bean>

如果有mybatis配置文件,那么直接添加一个在 TypeHandleys标签

<configuration>
	<TypeHandleys>
		<!-- 如果类里有注释的话这里就可以省去,如果没有建议是加上,即使不报错,也方便自己或者其他人查看 -->
		<typeHandler handler="com.ssm.common.model.utils.StringListTypeHandler" javaType="java.util.List" jdbcType="VARCHAR" />
	</TypeHandleys>
</configuration>

使用

第一种,在mapper.xml文件里

	<!-- 如果前两出都没有配置 类型的话,这里就要加上对应的类型 -->
	<update id="update">
		update table set cloum=#{cloum,typeHandler=com.ssm.common.model.utils.StringListTypeHandler} where id=#{id}
	</update>
	
	<!-- 也可以配置在查询和结果映射里,另外只有查询参数可以直接写在 sql 语句里,返回结果必须使用映射,同名且没有用到自定义转换类的或许可以不用配置,没有在具体测试 -->
	<parameterMap type="" id="">
		<parameter property="" javaType="" jdbcType="" typeHandler=""/>
	</parameterMap>
	<resultMap type="" id="">
		<result column="" property="" javaType="" jdbcType="" typeHandler=""/>
	</resultMap>

第二种,注解

mapper.java 里用注解的方式实现的查询参数直接按#{cloum,typeHandler=com.ssm.common.model.utils.StringListTypeHandler}这样的形式写上就行,这里要说的都是查询

	@Select("select * from table where cloum=#{cloum,typeHandler=com.ssm.common.model.utils.StringListTypeHandler}")
	@Results({、
		@Result(column="cloum",property="cloum",typeHandler=StringListTypeHandler.class)
	})
	public List<Role> select();

注意事项

  1. 注意:在这3个地方,至少有一处有声明类型,不然可能会报错
  2. 查询没报错,但是返回结果为 null ,先确定是否加载了,然后再看看是否没有声明 javaTypejdbcType 导致的
  3. jdbcType / javaType 不匹配或者类似的,看下查询语句里是否声明了 使用哪个转换类
上一篇:求最长公共子序列-DP问题


下一篇:.NET Web项目Excel导入与导出