当前位置: 首页 > 工具软件 > kot-mybatis > 使用案例 >

使用tk-mybatis(通用Mapper)踩过的那些坑

蔺山
2023-12-01

编写实体类时注意的事项

@Table(name = "tb_spec_param")
@Data
public class SpecParam {

    @Id
    @KeySql(useGeneratedKeys = true)
    private Long id;
    private Long cid;
    private Long groupId;
    private String name;
    private Boolean isNumeric;
    private String unit;
    private Boolean generic;
    private Boolean searching;
    @ColumnType(
            column = "segments",
            jdbcType = JdbcType.VARCHAR,
            typeHandler = StringArrayHandler.class
    )
    private String[] segments;
}

对于上面实体类的isNumeric,generic,searching这三个字段,可以看出共同点它们都是Boolean类型,但是如果你写成boolean类型的话,不管你数据库中值是什么,都会返回false。

同样对于int 类型的字段,也只能写成Interger。其他字段同理,只能使用引用数据类型,不能使用基本数据类型。

关于类型的转换,数据库字段类型和所需Java对象类型不符怎么办?

参考文章:https://elim.iteye.com/blog/1847854

建立TypeHandler

我们知道Java有java的数据类型,数据库有数据库的数据类型,在Java和数据库类型映射关系之间肯定有一个转换关系。在Mybatis中我们可以定义一个叫做TypeHandler类型处理器的东西,通过它可以实现Java类型跟数据库类型相互转换。

TypeHandler接口

    public interface TypeHandler<T> {  
       
        /** 
         * 用于定义在Mybatis设置参数时该如何把Java类型的参数转换为对应的数据库类型 
         * @param ps 当前的PreparedStatement对象 
         * @param i 当前参数的位置 
         * @param parameter 当前参数的Java对象 
         * @param jdbcType 当前参数的数据库类型 
         * @throws SQLException 
         */  
        void setParameter(PreparedStatement ps, int i, T parameter,  
               JdbcType jdbcType) throws SQLException;  
       
        /** 
         * 用于在Mybatis获取数据结果集时如何把数据库类型转换为对应的Java类型 
         * @param rs 当前的结果集 
         * @param columnName 当前的字段名称 
         * @return 转换后的Java对象 
         * @throws SQLException 
         */  
        T getResult(ResultSet rs, String columnName) throws SQLException;  
       
        /** 
         * 用于在Mybatis通过字段位置获取字段数据时把数据库类型转换为对应的Java类型 
         * @param rs 当前的结果集 
         * @param columnIndex 当前字段的位置 
         * @return 转换后的Java对象 
         * @throws SQLException 
         */  
        T getResult(ResultSet rs, int columnIndex) throws SQLException;  
       
        /** 
         * 用于Mybatis在调用存储过程后把数据库类型的数据转换为对应的Java类型 
         * @param cs 当前的CallableStatement执行后的CallableStatement 
         * @param columnIndex 当前输出参数的位置 
         * @return 
         * @throws SQLException 
         */  
        T getResult(CallableStatement cs, int columnIndex) throws SQLException;  
       
    }  

如果是自己写sql,其实挺容易的,自定义类型处理器实现TypeHandler就好了,也可以继承这个接口的抽象类BaseTypeHandler

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

BaseTypeHandlerTypeHandler接口的四个方法做了一个简单的选择,把null值的情况都做了一个过滤。核心的取值和设值的方法还是抽象出来供子类实现,使用BaseTypeHandler还有一个好处是它继承了另一个叫做TypeRererence的抽象类,通过TypeRererence的getRawType()方法可以获取到当前TypeHandler所使用泛型的原始类型。这对Mybatis在注册TypeHandler的时候是非常有好处的,在没有指定javaType的情况下,Mybatis在注册TypeHandler时可以通过它来获取当前TypeHandler所使用泛型的原始类型作为要注册的TypeHandler的javaType类型。

字符串和字符数组的转换

** 第一步:自定义一个类型处理器StringArrayHandler**

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class StringArrayHandler extends BaseTypeHandler<String[]> {
    @Override
    public String[] getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return getStringArray(rs.getString(columnName));
    }

    @Override
    public String[] getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return this.getStringArray(rs.getString(columnIndex));
    }

    @Override
    public String[] getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return this.getStringArray(cs.getString(columnIndex));
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
                                    String[] parameter, JdbcType jdbcType) throws SQLException {
        //由于BaseTypeHandler中已经把parameter为null的情况做了处理,所以这里我们就不用再判断parameter是否为空了,直接用就可以了
        StringBuffer result = new StringBuffer();
        for (String value : parameter)
            result.append(value).append(",");
        result.deleteCharAt(result.length()-1);
        ps.setString(i, result.toString());
    }

    private String[] getStringArray(String columnValue) {
        if (columnValue == null)
            return null;
        return columnValue.split(",");
    }
}

第二步:注册自定义的TypeHandler

建立了自己的TypeHandler之后就需要把它注册到Mybatis配置文件中,让Mybatis能够识别并使用它。注册TypeHandler主要有两种方式:

  • 通过在Mybatis配置文件中定义typeHandlers元素的子元素typeHandler来注册;
  • 另一种是在Mybatis配置文件中定义typeHandlers元素的子元素package来注册。

使用typeHandler子元素注册时只能注册一个typeHandler,而使用功能package子元素注册时,Mybatis会把指定包里面的所有typeHandler都注册了。

使用typeHandler子元素注册时,我们需要通过它的handler属性来指明当前要注册的typeHandler的全名称。另外还需要两个附加属性javaType用来指定java类型,和jdbcType用来指定对应的jdbc;类型。

使用package子元素注册时需要我们通过它的name属性来指定要扫描的包,如果这时候我们也需要指定对应TypeHandler的javaType和jdbcType的话就需要我们在自定义的TypeHandler类上使用注解来定义了。

1. 如果我们指定了javaType和jdbcType,那么Mybatis会注册一个对应javaType和jdbcType的TypeHandler

2. 如果我们只指定了javaType属性,这时候又分两种情况:

  • 如果通过注解形式在TypeHandler类上用@MappedJdbcTypes指定了对应的jdbcType,那么Mybatis会一一注册指定的javaType、jdbcType和TypeHandler组合。
    @MappedJdbcTypes({JdbcType.VARCHAR})  
    public class StringArrayTypeHandler implements TypeHandler<String[]> {  
        //..中间的实现代码省略了  
        //..  
    }  

然后在mybatis配置文件中这样注册

<typeHandlers>  
   <typeHandler handler="com.tiantian.mybatis.handler.StringArrayTypeHandler" javaType="[Ljava.lang.String;"/>  
</typeHandlers>

则Mybatis在实际注册时候是以javaType为String数组,jdbcType为VARCHAR来注册StringArrayTypeHandler的。

  • 如果没有使用@MaooedJdbcTypes注解指定对应的jdbcType,那么这时候Mybatis会把jdbcType置为null,然后注册一个javaType、null、TypeHandler的组合。

3. 既没有指定javaType属性,有没有指定jdbcType属性,或者只指定了jdbcType属性,又分三种情况

  1. 如果TypeHandler类上使用注解@MappedTypes指定了对应的javaType,那么Mybatis将一一利用对应javaType和TypeHandler去以2的方式进行注册。
    @MappedTypes({String[].class})  
    @MappedJdbcTypes({JdbcType.VARCHAR})  
    public class StringArrayTypeHandler implements TypeHandler<String[]> {  
    
    }  

然后在Mybatis的配置文件中注册它时既不指定它的javaType属性,也不指定jdbcType属性

<typeHandlers>  
   <typeHandler handler="com.tiantian.mybatis.handler.StringArrayTypeHandler"/>  
</typeHandlers> 
  • TypeHandler类上没有使用@MappedTypes指定对应的javaType时,如果当前的TypeHandler继承了TypeReference抽象类,Mybatis会利用TypeReference的getRawType()方法取到当前TypeHandler泛型对应的javaType类型,然后利用取得的javaType和TypeHandler以2的方式进行注册,同时还包括一个javaType为null以方式2进行的注册。TypeReference是Mybatis中定义的一个抽象类,主要是用来获取对应的泛型类型。

  • TypeHandler类上既没有标注@MappedTypes,又没有继承TypeReference抽象类。这种情况Mybatis会以null和null的组合注册该TypeHandler。
    使用package子元素注册的TypeHandler会以上面的方式3进行注册。

    <?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>  
       
        <properties resource="config/jdbc.properties"></properties>  
        <typeAliases>  
           <package name="com.tiantian.mybatis.model"/>  
        </typeAliases>  
        <typeHandlers>  
           <typeHandler handler="com.tiantian.mybatis.handler.StringArrayTypeHandler" javaType="[Ljava.lang.String;" jdbcType="VARCHAR"/>  
        </typeHandlers>  
        <environments default="development">  
           <environment id="development">  
               <transactionManager type="JDBC" />  
               <dataSource type="POOLED">  
                  <property name="driver" value="${jdbc.driver}" />  
                  <property name="url" value="${jdbc.url}" />  
                  <property name="username" value="${jdbc.username}" />  
                  <property name="password" value="${jdbc.password}" />  
               </dataSource>  
           </environment>  
        </environments>  
        <mappers>  
           <mapper resource="com/tiantian/mybatis/mapper/UserMapper.xml"/>  
           <package name="com.tiantian.mybatis.mapperinterface"/>  
        </mappers>  
    </configuration>  

然后在mapperxml文件中定义resultMap映射

<resultMap id="result" type="com.leyou.item.pojo.SpecParam">
        <id column="id" property="id"/>
        <result column="segments" property="segments" javaType="[Ljava.lang.String;"
                jdbcType="VARCHAR" />
    </resultMap>

如果使用的是通用Mapper,即没有Mybatis配置文件和xml接口文件

通用Mapper提供了一个@ColumnType注解来指定类型转换器

 @ColumnType(
            column = "segments", //字段名
            jdbcType = JdbcType.VARCHAR, //jdbc类型,即数据库中的字段类型
            typeHandler = StringArrayHandler.class  //指定类型转换器
    )
    private String[] segments;

其它常用自定义类型转换器

参考文章:https://www.cnblogs.com/myadmin/p/5064274.html

  • 存储到数据库, 将LONG数组转换成字符串;从数据库获取数据, 将字符串转为LONG数组
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import com.winturn.utils.CommonJsonUtil;

/**
 * <p>Class: ArrayLongTypeHandler.java</p>
 * <p>Description: 存储到数据库, 将LONG数组转换成字符串;
 *                 从数据库获取数据, 将字符串转为LONG数组.
 </p>*/
public class ArrayLongTypeHandler extends BaseTypeHandler<Object> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, CommonJsonUtil.stringify(parameter));
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return CommonJsonUtil.parse3(rs.getString(columnName), Object.class);
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parse3(rs.getString(columnIndex), Object.class);
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parse3(cs.getString(columnIndex), Object.class);
    }

}
  • 存储到数据库, 将基本数据数组转换成字符串;从数据库获取数据, 将字符串根据’,'拆分,转为数组.
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import com.winturn.utils.CommonJsonUtil;

/**
 * <p>Class: ArrayStringTypeHandler.java</p>
 * <p>Description: 存储到数据库, 将基本数据数组转换成字符串;
 *                 从数据库获取数据, 将字符串根据','拆分,转为数组.</p>
 *
 * 
 */
public class ArrayStringTypeHandler extends BaseTypeHandler<Object> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Object parameter,
            JdbcType jdbcType) throws SQLException {
        ps.setString(i, CommonJsonUtil.stringify(parameter));
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return CommonJsonUtil.parse2(rs.getString(columnName), Object.class);
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parse2(rs.getString(columnIndex), Object.class);
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parse2(cs.getString(columnIndex), Object.class);
    }

}
  • .jsonarray 格式的字符串转换为相应的数组
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import com.winturn.utils.CommonJsonUtil;


/**
 * <p>Class: ArrayIntegerTypeHandler.java</p>
 * <p>Description: jsonarray 格式的字符串转换为相应的数组 </p>
 * 
 */
public class JsonArrayTypeHandler extends BaseTypeHandler<Object> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, CommonJsonUtil.stringify(parameter));
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return CommonJsonUtil.parseJsonToArray(rs.getString(columnName), Object.class);
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parseJsonToArray(rs.getString(columnIndex), Object.class);
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parseJsonToArray(cs.getString(columnIndex), Object.class);
    }

}
  • 将Float类型的数组装换成字符创进行存储
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;

import com.winturn.utils.CommonJsonUtil;

/**
 * <p>Filename:JsonFloatTypeHandler.java</p>
 * <p>Description: 将float类型数组装换成字符串

</p>

 * 
 */
public class JsonFloatTypeHandler extends BaseTypeHandler<Object> {

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i,
            Object parameter, JdbcType jdbcType) throws SQLException {
        ps.setString(i, CommonJsonUtil.stringifyObject(parameter));
    }

    @Override
    public Object getNullableResult(ResultSet rs, String columnName)
            throws SQLException {
        return CommonJsonUtil.parseJsonToFloat(rs.getString(columnName), Object.class);
    }

    @Override
    public Object getNullableResult(ResultSet rs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parseJsonToFloat(rs.getString(columnIndex), Object.class);
    }

    @Override
    public Object getNullableResult(CallableStatement cs, int columnIndex)
            throws SQLException {
        return CommonJsonUtil.parseJsonToFloat(cs.getString(columnIndex), Object.class);
    }
}
  • 将map装换成字符串存储到数据库,取出时将字符串装换成map
import java.sql.CallableStatement;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Map;

import org.apache.ibatis.type.BaseTypeHandler;
import org.apache.ibatis.type.JdbcType;
import org.codehaus.jackson.map.ObjectMapper;

import com.winturn.exceptions.RolerServiceException;
import com.winturn.utils.JsonMapUtil;
/**
 * 
* @ClassName: JsonMapTypeHandler 
* @Description: 将map装换成数组存储数据库,取出时将字符串装换成map
* @author sgl
* @date 2015年12月21日 下午6:22:50
 */
public class JsonMapTypeHandler extends BaseTypeHandler<Map<String, Object>> {

    ObjectMapper mapper = new ObjectMapper();

    @Override
    public Map<String, Object> getNullableResult(ResultSet rs, String columnName) {
        try {
            String value = rs.getString(columnName);
            return mapper.readValue(value, Map.class);
        } catch (Exception e) {

        }
        return null;
    }

    @Override
    public Map<String, Object> getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        try {
            String value = rs.getString(columnIndex);
            return mapper.readValue(value, Map.class);
        } catch (Exception e) {

        }
        return null;
    }

    @Override
    public Map<String, Object> getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        try {
            String value = cs.getString(columnIndex);
            return mapper.readValue(value, Map.class);
        } catch (Exception e) {

        }
        return null;
    }

    @Override
    public void setNonNullParameter(PreparedStatement ps, int i, Map<String, Object> parameter, JdbcType jdbcType)
            throws SQLException {
        if (parameter == null) {
            ps.setNull(i, Types.VARCHAR);
        } else {
            try {
                ps.setString(i, JsonMapUtil.getJsonStrByMap(parameter));
            } catch (RolerServiceException e) {
                e.printStackTrace();
            }
        }

    }
}

Mybatis中自动注册TypeHandler

    register(Boolean.class, new BooleanTypeHandler());  
    register(boolean.class, new BooleanTypeHandler());  
    register(Byte.class, new ByteTypeHandler());  
    register(byte.class, new ByteTypeHandler());  
    register(Short.class, new ShortTypeHandler());  
    register(short.class, new ShortTypeHandler());  
    register(Integer.class, new IntegerTypeHandler());  
    register(int.class, new IntegerTypeHandler());  
    register(Long.class, new LongTypeHandler());  
    register(long.class, new LongTypeHandler());  
    register(Float.class, new FloatTypeHandler());  
    register(float.class, new FloatTypeHandler());  
    register(Double.class, new DoubleTypeHandler());  
    register(double.class, new DoubleTypeHandler());  
    register(String.class, new StringTypeHandler());  
    register(String.class, JdbcType.CHAR, new StringTypeHandler());  
    register(String.class, JdbcType.CLOB, new ClobTypeHandler());  
    register(String.class, JdbcType.VARCHAR, new StringTypeHandler());  
    register(String.class, JdbcType.LONGVARCHAR, new ClobTypeHandler());  
    register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());  
    register(String.class, JdbcType.NCHAR, new NStringTypeHandler());  
    register(String.class, JdbcType.NCLOB, new NClobTypeHandler());  
    register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());  
    register(BigInteger.class, new BigIntegerTypeHandler());  
    register(BigDecimal.class, new BigDecimalTypeHandler());  
    register(Byte[].class, new ByteObjectArrayTypeHandler());  
    register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());  
    register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());  
    register(byte[].class, new ByteArrayTypeHandler());  
    register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());  
    register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());  
    register(Object.class, UNKNOWN_TYPE_HANDLER);  
    register(Object.class, JdbcType.OTHER, UNKNOWN_TYPE_HANDLER);  
    register(Date.class, new DateTypeHandler());  
    register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());  
    register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());  
    register(java.sql.Date.class, new SqlDateTypeHandler());  
    register(java.sql.Time.class, new SqlTimeTypeHandler());  
    register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());  
    register(Character.class, new CharacterTypeHandler());  
    register(char.class, new CharacterTypeHandler());  
 类似资料: