在做開發時,我們經常會遇到這樣一些問題,比如我有一個Java中的Date資料型別,我想將之存到資料庫的時候存成一個1970年至今的毫秒數,怎麼實現?再比如我有一個User類,User類中有一個屬性叫做interest,這個屬性用來描述使用者的愛好,它的資料型別是一個List集合,那麼我想在把這個List集合存入資料庫的時候能夠自動的變成{XXX,XXX,XXX}
這樣一個字串然後存起來,當我從資料庫讀取的時候也是讀取到這樣一個字串,讀取成功之後再自動的將之轉為一個List集合,OK,以上兩種需求用我們傳統的資料庫讀寫操作肯定都是可以實現的,只不過工作量略大,在mybatis中有一個功能略強大的typeHandler專門用來解決資料庫中的資料型別和Java中的資料型別之間的轉化問題,那麼我們今天以上面兩種需求為例,來看看typeHandler要怎麼使用。如果還有小夥伴對mybatis不太熟悉,建議先閱讀一下前面幾篇部落格(初識mybatis/初識mybatis(二)/mybatis常用配置/mybatis對映器配置細則),本文的內容將在前面幾篇部落格的基礎上展開,當然如果小夥伴有mybatis基礎,那直接往下看即可。
事實上,mybatis本身已經為我們提供了許多typeHandler了,系統提供的typeHandler能夠滿足我們日常開發中的大部分需求,如上這兩種特殊的需求就需要我們自己去定義typeHandler了。
#日期的轉換
先來看日期的轉換,假設我現在建立一張表,如下:
public class User {
private Long id;
private String username;
private String password;
private Date regTime;
//省略getter/setter
}
複製程式碼
這個JavaBean中也有一個regTime欄位,不同的是這裡的資料型別是Date,OK,如果我不做任何特殊處理,直接像初識mybatis(二)這篇部落格中介紹的那樣向資料庫插入資料,也是可以插入成功的,但是插入成功後是這樣:
這個當然不是我想要的,我希望存到資料庫裡的是這樣的: 就是我直接向資料庫寫資料,要寫的是一個Date物件,但是寫到資料庫之後這個Date物件就變成了Date物件所描述的時間到1970年的秒數了,然後當我從資料庫讀取這個秒數之後,系統又會自動幫我將這個秒數轉為Date物件,就是這樣兩個需求。這個時候,我們要做的事情其實很簡單,那就是自定義typeHandler,自定義typeHandler我們有兩種方式,一種是實現TypeHandler介面,還有一種簡化的寫法就是繼承自BaseTypeHandler類,我這裡先以第二種為例來進行說明。 ##自定義typeHandler繼承自BaseTypeHandler@MappedJdbcTypes({JdbcType.VARCHAR})
@MappedTypes({Date.class})
public class MyDateTypeHandler extends BaseTypeHandler<Date> {
public void setNonNullParameter(PreparedStatement preparedStatement, int i, Date date, JdbcType jdbcType) throws SQLException {
preparedStatement.setString(i, String.valueOf(date.getTime()));
}
public Date getNullableResult(ResultSet resultSet, String s) throws SQLException {
return new Date(resultSet.getLong(s));
}
public Date getNullableResult(ResultSet resultSet, int i) throws SQLException {
return new Date(resultSet.getLong(i));
}
public Date getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
return callableStatement.getDate(i);
}
}
複製程式碼
關於這個類我說如下幾點:
1.@MappedJdbcTypes定義的是JdbcType型別,這裡的型別不可自己隨意定義,必須要是列舉類org.apache.ibatis.type.JdbcType所列舉的資料型別。 2.@MappedTypes定義的是JavaType的資料型別,描述了哪些Java型別可被攔截。 3.在我們啟用了我們自定義的這個TypeHandler之後,資料的讀寫都會被這個類所過濾 4.在setNonNullParameter方法中,我們重新定義要寫往資料庫的資料。 5.在另外三個方法中我們將從資料庫讀出的資料型別進行轉換。
##在Mapper中進行配置 自定義好了typeHandler之後,接下來我們需要在userMapper.xml中進行簡單的配置,首先我們可以像上文說的,配置resultMap,如下:
<resultMap id="userResultMap" type="org.sang.bean.User">
<result typeHandler="org.sang.db.MyDateTypeHandler" column="regTime" javaType="java.util.Date"
jdbcType="VARCHAR"
property="regTime"/>
</resultMap>
複製程式碼
配置resultMap的時候我們指定了javaType和jdbcType,同時也指定了處理的typeHandler,然後在select中使用這個resultMap:
<select id="getUser" resultMap="userResultMap">
select * from user4
</select>
複製程式碼
但是這種方式有一個缺點那就是隻適用於查詢操作,即在查詢的過程中系統會啟用我們自定義的typeHandler,會將秒數轉為Date物件,但是在插入的時候卻不會啟用我們自定義的typeHandler,想要在插入的時候啟用自定義的typeHandler,需要我們在insert節點中簡單配置一下,如下:
<insert id="insertUser" parameterType="org.sang.bean.User">
INSERT INTO user4(username,password,regTime) VALUES (#{username},#{password},#{regTime,javaType=Date,jdbcType=VARCHAR,typeHandler=org.sang.db.MyDateTypeHandler})
</insert>
複製程式碼
也可以只配置javaType和jdbcType,如下:
<insert id="insertUser2">
INSERT INTO user4(username,password,regTime) VALUES (#{username},#{password},#{regTime,javaType=Date,jdbcType=VARCHAR})
</insert>
複製程式碼
或者只配置typeHandler:
<insert id="insertUser3">
INSERT INTO user4(username,password,regTime) VALUES (#{username},#{password},#{regTime,typeHandler=org.sang.db.MyDateTypeHandler})
</insert>
複製程式碼
這三種效果都是一樣的,都是在插入的時候將資料Date物件轉為秒數。OK,如此之後,我們就可以實現將Date物件插入資料庫之後變秒數以及將資料庫中的秒數讀取之後自動轉為Date物件了。我們來看一個簡單的測試:
@Test
public void test2() {
SqlSession sqlSession = null;
try {
sqlSession = DBUtils.openSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = new User();
user.setPassword("222222");
user.setUsername("李四");
Date regTime = new Date();
user.setRegTime(regTime);
userMapper.insertUser(user);
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
複製程式碼
插入結果如下:
讀取程式碼:
@Test
public void test1() {
SqlSession sqlSession = null;
try {
sqlSession = DBUtils.openSqlSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> list = userMapper.getUser();
for (User user : list) {
System.out.println(user);
}
sqlSession.commit();
} catch (Exception e) {
e.printStackTrace();
sqlSession.rollback();
} finally {
if (sqlSession != null) {
sqlSession.close();
}
}
}
複製程式碼
讀取結果如下:
OK,結果上面幾步配置我們就完美的解決了讀寫時的資料轉換問題了,讀取時的資料轉換除了我們上面介紹的定義resultMap然後在select節點中引用這種方式之外,也可以使用下面這種方式,注意下面這種方式只能解決讀取時的資料轉換問題 ##在配置檔案中註冊typeHandler 我們需要在我們的mybatis配置檔案中註冊typeHandler,註冊有兩種不同的方式,可以像下面這樣一個類一個類的註冊:
<typeHandlers>
<typeHandler handler="org.sang.db.MyDateTypeHandler"/>
</typeHandlers>
複製程式碼
也可以直接註冊一個包中所有的typeHandler,系統在啟動時會自動掃描包下的所有檔案,如下:
<typeHandlers>
<package name="org.sang.db"/>
</typeHandlers>
複製程式碼
這樣配置完成之後,我們的目的就達到了,當我們進行資料庫的讀取操作的時候,秒數就會自動轉為Date物件。
#小結 OK,經過上面的介紹,想必小夥伴對typeHandler的使用已經有一定了解了,總結一下就是讀取時的配置要和插入時的配置分貝來做,讀取時資料轉換我們有兩種配置方式,分別是resultMap和在mybatis配置檔案中配置typeHandlers,插入時的配置就是在insert節點中進行配置。
#List集合的轉換
OK,如果小夥伴們學會了如何把Date轉為秒數,那麼對於List集合的轉換我就不再贅述了,道理都是一樣的,大家可以可以直接在文末下載Demo,Demo中有List集合轉換的案例。 這裡給大家看兩張效果圖吧: List集合存入資料庫之後變成這樣:
讀取出來之後又自動轉為List集合了,下圖是查詢操作,實體類,和查詢結果:OK,以上就是我們對typeHandler的一個簡單介紹。
本文案例下載: 本文案例GitHub地址github.com/lenve/JavaE…。
以上。
更多資料,請關注公眾號牧碼小子,回覆 Java, 獲取鬆哥為你精心準備的Java乾貨!
參考資料: 《深入淺出MyBatis 技術原理與實戰》第三章