平常java開發中常常有這種需求,用0或者1這些代碼(不侷限於數字)來表示某種狀態。好比用0表示女性,用1來表示男性。並且寫入數據庫多是一個標識,從數據庫讀取又還原爲具體的說明。並且通常狀況下爲了更好理解或者消除魔法值,一般的處理方案是定義一個枚舉:java
有些枚舉是這樣定義的git
public enum GenderType{ FEMALE,MALE,UNKNOWN }
那麼一般不少人會這麼入庫(java僞代碼)spring
if(GenderType.MALE){ // 寫入 1 }else if(GenderType.FEMALE){ // 寫入 0 }else{ //也多是泰國回來的 那就 2 }
讀取的時候要麼一樣按照上面的再反向處理一次或者使用數據庫sql語法case when
來直接寫入DTOsql
CASE gender WHEN 1 THEN '男' WHEN 0 THEN '女' ELSE '未知' END
這種處理方式看起來不是很優雅。並且多了不少的判斷和處理邏輯,和咱們的業務並非很是相關。因此咱們能夠選擇更好的處理方式。數據庫
若是你ORM框架用的是Mybatis。那麼將很容易經過TypeHandler<T>接口解決這個問題。apache
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; T getResult(CallableStatement cs, int columnIndex) throws SQLException; }
源碼分析:springboot
咱們發現TypeHandler有一個實現類EnumOrdinalTypeHandler。字面意思是能夠經過枚舉的序號來處理類型。mybatis
@Override public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { ps.setInt(i, parameter.ordinal()); }
咱們先不考慮setNull的狀況。經過此方法咱們發現確實存入的是枚舉的順序值(順序從0開始),拿上面的例子來講 若是是GenderType.FEMALE是0,若是是GenderType.MALE是1,可是當GenderType.UNKNOWN時存入的是2。取的時候也是天然反向處理爲具體的GenderType枚舉。app
咱們還發現有另一個枚舉類型處理器。它的set方法是這樣的:框架
@Override public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException { if (jdbcType == null) { ps.setString(i, parameter.name()); } else { ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); // see r3589 } }
咱們不考慮jdbcType問題發現都是將Enum.name()的值寫入數據庫。拿上面的例子來講 若是是GenderType.FEMALE是FEMALE,若是是GenderType.MALE是MALE,可是當GenderType.UNKNOWN時存入的是UNKNOWN。讀庫是經過Enum.valueOf(Class<T> enumType,String name)來進行反轉操做。
若是說咱們的枚舉類型或者說咱們使用其餘方式來處理類別轉換怎麼辦?固然Mybatis不會幫你幹這麼具體的事情。須要你本身來實現了。咱們還拿枚舉做爲例子,而後模仿上面的兩種TypeHandler。 仍是拿開始的例子來講一般我我的比較喜歡這麼定義枚舉:
public enum GenderTypeEnum { /** * female. */ FEMALE(0, "女"), /** * male. */ MALE(1,"男"), /** * unknown. */ UNKNOWN(2, "未知"); private int value; private String description; GenderType(int value, String description) { this.value = value; this.description = description; } public int value() { return this.value; } public String description() { return this.description; } }
經過繼承BaseTypeHandler實現該抽象類的3個鉤子方法就好了:
@MappedTypes({GenderTypeEnum.class}) @MappedJdbcTypes({JdbcType.INTEGER}) public class GenderTypeEnumTypeHandler extends BaseTypeHandler<GenderTypeEnum> { @Override public void setNonNullParameter(PreparedStatement ps, int i, GenderTypeEnum parameter, JdbcType jdbcType) throws SQLException { if (jdbcType == null) { ps.setInt(i, parameter.value()); } else { // see r3589 ps.setObject(i, parameter.value(), jdbcType.TYPE_CODE); } } @Override public GenderTypeEnum getNullableResult(ResultSet rs, String columnName) throws SQLException { return getGenderType(rs.getInt(columnName)); } @Override public GenderTypeEnum getNullableResult(ResultSet rs, int columnIndex) throws SQLException { return getGenderType(rs.getInt(columnIndex)); } @Override public GenderTypeEnum getNullableResult(CallableStatement cs, int columnIndex) throws SQLException { return getGenderType(cs.getInt(columnIndex)); } private GenderTypeEnum getGenderType(int value) { Class<GenderTypeEnum> genderTypeClass = GenderTypeEnum.class; return Arrays.stream(genderTypeClass.getEnumConstants()) .filter(genderType -> genderType.value() == value) .findFirst().orElse(GenderTypeEnum.UNKNOWN); } }
TypeHandler 實現寫好了,那麼如何讓其發揮做用呢?咱們接着往下走。
TypeHandler做用是javaType和jdbcType相互轉換。因此在聲明一個TypeHandler的時候必定要明確該TypeHandler處理的這兩種類型。這是必需要明確的原則。MyBatis不會經過窺探數據庫元信息來決定使用哪一種JDBC類型,因此你必須在參數和結果映射中指明何種類型的字段,使其可以綁定到正確的類型處理器上。MyBatis直到語句被執行時才清楚數據類型。 經過上述例子中的@MappedJdbcTypes和@MappedTypes來進行綁定類型轉換關係,也能夠經過xml的typeHandler元素中的jdbcType或者javaType來指定。若是同時指定,xml的優先級要高。 注意有可能你會覆蓋內置的TypeHandler。因此自定義時必定要去了解Mybatis提供的一些默認處理器。避免對其餘業務的影響。因此使用自定義TypeHandler很重要的一個原則就是必定要聲明JavaType和JdbcType.上面這些雖然比較生澀可是對於使用好TypeHandler很是重要。接下來咱們來說講具體的配置。
咱們這裏只講xml中的配置:
<resultMap id="StudentMap" type="cn.felord.mybatis.entity.Student"> <id column="student_id" property="studentId"/> <result column="student_name" property="studentName"/> <result column="gender" property="genderType" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/> <result column="age" property="age"/> </resultMap>
<insert id="saveStu"> insert into student (student_name, gender, age) values (#{studentName}, #{genderType,javaType=cn.felord.mybatis.enums.GenderTypeEnum,jdbcType=INTEGER,typeHandler=cn.felord.mybatis.type.GenderTypeEnumTypeHandler}, #{age}) </insert>
若是註冊了別名均可以使用別名。上面的好處就是不用在TypeHandlerRegistry中進行註冊。
在配置中聲明註冊TypeHandler,而後Mybatis根據兩種類型會自動匹配。因此這裏仍是要強調2.5中的核心要點。
<typeHandlers> <typeHandler jdbcType="JdbcType枚舉存在的枚舉" javaType="typeAliases的別名或者全限定類名" handler="類全限定名"/> <package name="指定全部typeHandler所在的包的包名"/> </typeHandlers>
若是你註冊了TypeHandler。在Mapper.xml中只須要聲明jdbcType和javaType,無需再聲明具體的typeHandler。Mybatis會自動經過jdbcType、javaType來映射到具體註冊的TypeHandler上去 。就像下面的例子
<insert id="saveAutomaticStu"> insert into student (student_name, gender, age) values (#{studentName}, #{genderType,javaType=cn.felord.mybatis.enums.GenderTypeEnum,jdbcType=INTEGER}, #{age}) </insert>
今天咱們學習了mybatis開發中如何經過使用類型處理器進行類型的轉換處理,如何處理枚舉,如何自定義處理器並使用它。相信對你在java開發過程當中會有很大的幫助。相關的代碼在個人碼雲倉庫中:https://gitee.com/felord/mybatis-test.git
多多關注個人公衆號:碼農小胖哥,能夠獲得更加及時的資訊和反饋。