mybatis偵探實錄:typehandler薛定諤之謎
1、案件背景
前天,一個涉案人員(同事)提到,在mysql的資料庫中,dba推薦的做法是所有的varchar欄位都設定成不能為空,並且預設值為empty string,這樣對查詢效能有一定的幫助,設定的sql片段是這樣的:
`field_name` varchar(255) NOT NULL DEFAULT ''
問我在mybatis裡面這種情況怎麼設定。我假裝思考,然後飛快的開啟谷歌,搜尋答案,得到了一個詞,typehandler。typehandler是mybatis用來針對java型別和資料庫型別對不上時做處理工作的類,當前的情況就是如果我輸入的型別是null,那麼在資料庫要自動轉換成空字串,不能直接把null塞到資料庫欄位裡面。typehandler的做法是寫一個類來實現TypeHandler介面,於是我就寫一個簡單的:
@MappedTypes(value = String.class) public class NullToEmptyStringTypeHandler implements TypeHandler<String> { @Override public void setParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException { System.out.println("into NullToEmptyStringTypeHandler"); if(parameter == null && jdbcType == JdbcType.VARCHAR){//判斷傳入的引數值是否為null ps.setString(i,"");//設定當前引數的值為空字串 }else{ ps.setString(i,parameter);//如果不為null,則直接設定引數的值為value } } @Override public String getResult(ResultSet rs, String columnName) throws SQLException { return rs.getString(columnName); } @Override public String getResult(ResultSet rs, int columnIndex) throws SQLException { return rs.getString(columnIndex); } @Override public String getResult(CallableStatement cs, int columnIndex) throws SQLException { return cs.getString(columnIndex); } }
重點在於註解@MappedTypes(value = String.class)和setParameter方法,我的理解就是如果我傳進來的是String型別的欄位,在setParameter的引數JdbcType 裡面判斷出來是VARCHAR的話,那就直接填一個空字元進去,完事大吉。
這個類還需要配置一下,讓mybatis到哪裡去找到它,我用的是springboot,很簡單的配置,在application.properties裡面加這一句就好了:
mybatis.type-handlers-package=com.wphmoon.lesson.common.typehandler
com.wphmoon.lesson.common.typehandler就是NullToEmptyStringTypeHandler 所在的包名,這個包名下的TypeHandler都會被觸發執行。我以為事情就這麼簡單,但實際上就出問題了。
2、案發現場
為了驗證NullToEmptyStringTypeHandler是否可用,我寫了一個簡單的表來驗證,表結構如下
CREATE TABLE `my_user` (
`id` bigint(20) NOT NULL AUTO_INCREMENT ,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '姓名' ,
`nickname` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '暱稱' ,
`age` int(11) NULL DEFAULT NULL COMMENT '年齡' ,
`birthday` datetime NULL DEFAULT NULL COMMENT '生日' ,
`memo` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' COMMENT '備註' ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB;
我又弄了個配套的資料物件和mapper類:
public class MyUser implements Serializable{
public long id;
public String name;
public String nickname;
public int age;
public Date birthday;
public String memo;
//get,set.......
}
@Mapper
public interface MyUserMapper {
@Select("SELECT * FROM MY_USER WHERE NAME = #{name}")
MyUser findByName(@Param("name") String name);
@Select("SELECT * FROM MY_USER WHERE ID = #{id}")
MyUser findById(@Param("id") Long id);
@Insert("INSERT INTO MY_USER(NAME, NICKNAME,AGE,BIRTHDAY,MEMO) VALUES(#{name},#{nickname},#{age},#{birthday},#{memo})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
void insert(MyUser myUser);
}
最後,我搞了一個controller來執行:
@RestController
@RequestMapping("/my")
public class MyController {
@Autowired
private MyUserMapper myUserMapper;
@RequestMapping(path="/insert2MyUser")
public String insert2Myuser(MyUser myUser) {
myUserMapper.insert(myUser);
return "";
}
}
執行http://localhost:8080/my/insert2MyUser?age=1後的結果有喜有憂,得到的console輸出是這樣的:
into NullToEmptyStringTypeHandler,jdbcType=OTHER
into NullToEmptyStringTypeHandler,jdbcType=OTHER
into NullToEmptyStringTypeHandler,jdbcType=OTHER
這是什麼鬼,jdbcType完全不是我以為的VARCHAR型別。不過好歹NullToEmptyStringTypeHandler 被觸發執行了,如果我不需要檢驗jdbcType的話,這個功能算是實現了,我把所有的null值直接替換成空字串就行了。
但我好死不死,想看下如果我是用xml來配置mybatis的sql情況會不會有所不同,我搞過了一個表,用xml的方式來實現,表的結構如下:
CREATE TABLE `my_task` (
`id` bigint(20) NOT NULL AUTO_INCREMENT ,
`title` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
`description` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL DEFAULT '' ,
`user_id` bigint(20) NULL DEFAULT NULL ,
PRIMARY KEY (`id`)
)
ENGINE=InnoDB;
mapper檔案和資料物件檔案如下:
@Mapper
public interface MyTaskMapper {
long countByExample(MyTaskExample example);
int deleteByExample(MyTaskExample example);
int deleteByPrimaryKey(Long id);
int insert(MyTask record);
int insertSelective(MyTask record);
MyTask selectOneByExample(MyTaskExample example);
List<MyTask> selectByExample(MyTaskExample example);
MyTask selectByPrimaryKey(Long id);
int updateByExampleSelective(@Param("record") MyTask record, @Param("example") MyTaskExample example);
int updateByExample(@Param("record") MyTask record, @Param("example") MyTaskExample example);
int updateByPrimaryKeySelective(MyTask record);
int updateByPrimaryKey(MyTask record);
}
public class MyTask implements Serializable{
private Long id;
private String title;
private String description;
private Long userId;
//get,set.......
還有mapper的xml檔案,這個太長了,我就只列insert語句的部分
<insert id="insert" parameterType="com.wphmoon.lesson.domain.MyTask">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
SELECT LAST_INSERT_ID()
</selectKey>
insert into my_task (title, description, user_id
)
values (#{title,jdbcType=VARCHAR}, #{description,jdbcType=VARCHAR}, #{userId,jdbcType=BIGINT}
)
</insert>
我同樣在controller中寫了一段新增記錄的程式碼:
@RequestMapping(path="/insert2MyTask")
public String insert2MyTask(MyTask myTask) {
return String.valueOf(myTaskMapper.insert(myTask));
}
執行http://localhost:8080/my/insert2MyTask?title=test2&userId=2後滿心歡喜的等待NullToEmptyStringTypeHandler的觸發,結果慘案發生了,NullToEmptyStringTypeHandler並沒有被觸發,毫無動靜。難道是xml的配置方式和註解的方式有啥不同?或者有什麼地方出錯了,是性格的扭曲還是人性的喪失啥原因呢,讓我們再縷一遍案情:
1)NullToEmptyStringTypeHandler在被MyUserMapper(註解方式)執行的時候被觸發了,但是引數jdbcType為OTHER型別,而不是我們以為的VARCHAR型別
2)NullToEmptyStringTypeHandler在MyTaskMapper(xml方式)執行的時候沒有被觸發。
這是為啥呢,讓我們開始破案。
3、案情追查
我一開始是被兩種mapper不同的實現方式所迷惑,一種用註解@Insert,一種用xml配置insert,難道他們的實現方法有很大不同,我通過兩種方法來追查,一種是DEBUG,我設定斷點,從myUserMapper.insert()到MapperMethod.execute(),SqlSessionTemplate.invoke(),然後就走到NullToEmptyStringTypeHandler裡面去了,而myTaskMapper則完全忽略了NullToEmptyStringTypeHandler,看來debug走不通。
我又啟動了B計劃,把日誌開到TRACE級別,對比兩者的日誌,一行行做對比,但非常的絕望,兩者並無不同。大家欣賞下這個日誌:
這是執行MyUserMapper.insert
2019-05-27 12:20:22.390 DEBUG 13836 --- [nio-8080-exec-3] c.w.lesson.mapper.MyUserMapper.insert : ==> Preparing: INSERT INTO MY_USER(NAME, NICKNAME,AGE,BIRTHDAY,MEMO) VALUES(?,?,?,?,?)
into NullToEmptyStringTypeHandler,jdbcType=OTHER
into NullToEmptyStringTypeHandler,jdbcType=OTHER
into NullToEmptyStringTypeHandler,jdbcType=OTHER
2019-05-27 12:20:22.392 DEBUG 13836 --- [nio-8080-exec-3] c.w.lesson.mapper.MyUserMapper.insert : ==> Parameters: null, null, 1(Integer), null, null
這是執行MyTaskMapper.insert的日誌,完美的略過了NullToEmptyStringTypeHandler,完全沒有觸發
2019-05-27 12:23:08.226 DEBUG 1628 --- [nio-8080-exec-3] c.w.lesson.mapper.MyTaskMapper.insert : ==> Preparing: insert into my_task (title, description, user_id ) values (?, ?, ? )
2019-05-27 12:23:08.227 DEBUG 1628 --- [nio-8080-exec-3] c.w.lesson.mapper.MyTaskMapper.insert : ==> Parameters: test2(String), null, 2(Long)
此路不通後,我開始轉換了一個探案思維,考慮到xml配置的mapper也還是需要用到typeHandler,那麼它需要的時候是怎麼辦的呢,我再次動用了偵探大腦(google),發現了在xml裡面配置如下:
<insert id="insert" parameterType="com.wphmoon.lesson.domain.MyTask">
<selectKey keyProperty="id" order="AFTER" resultType="java.lang.Long">
SELECT LAST_INSERT_ID()
</selectKey>
insert into my_task (title, description, user_id
)
values (#{title,jdbcType=VARCHAR,typeHandler=com.wphmoon.lesson.common.typehandler.NullToEmptyStringTypeHandler}, #{description,jdbcType=VARCHAR}, #{userId,jdbcType=BIGINT}
)
</insert>
可以直接在欄位裡面配置typeHandler,我嘗試在title欄位裡面配置NullToEmptyStringTypeHandler,然後試下能不能觸發NullToEmptyStringTypeHandler。
2019-05-27 17:15:09.546 DEBUG 17400 --- [nio-8080-exec-4] c.w.lesson.mapper.MyTaskMapper.insert : ==> Preparing: insert into my_task (title, description, user_id ) values (?, ?, ? )
into NullToEmptyStringTypeHandler,jdbcType=VARCHAR
結論是可以觸發,但我敏銳(cidun)的偵探嗅覺發現竟然連jdbcType都可以正確拿到,難道是以為我的xml裡面寫了
#{title,jdbcType=VARCHAR......
也就是說,如果我把之前的註解裡面也把jdbcType寫上去,應該也是可以的。我立即行動,改了下MyUserMapper的註解程式碼
@Insert("INSERT INTO MY_USER(NAME, NICKNAME,AGE,BIRTHDAY,MEMO) VALUES(#{name,jdbcType=VARCHAR},#{nickname},#{age},#{birthday},#{memo})")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
void insert(MyUser myUser);
我在註解的name欄位後面加上了jdbcType=VARCHAR,看看NullToEmptyStringTypeHandler能不能取到:
......
結果是不可以,現在就很尷尬了,不加jdbcType,可以觸發NullToEmptyStringTypeHandler,加了jdbcType,反而不能觸發了,我仿照xml的樣子,把NullToEmptyStringTypeHandler寫到註解的sql裡面去試下:
@Insert("INSERT INTO MY_USER(NAME, NICKNAME,AGE,BIRTHDAY,MEMO) VALUES(#{name,jdbcType=VARCHAR,typeHandler=com.wphmoon.lesson.common.typehandler.NullToEmptyStringTypeHandler},#{nickname},#{age},#{birthday},#{memo})")
這下觸發了NullToEmptyStringTypeHandler,並且能夠得到jdbcType的值為VARCHAR。
到這裡,我得到的結論是,如果在欄位裡面寫上去typeHandler具體處理類(NullToEmptyStringTypeHandler),那麼無論寫不寫jdbcType都會觸發具體TypeHandler處理類,如果不在欄位裡面寫,那麼寫了jdbcType反而不會觸發。這是為什麼呢?
我繼續開啟我的偵探直覺。這次不是去google,而是去看了下@MappedTypes(NullToEmptyStringTypeHandler頭頂上的)註解的原始碼,結果原始碼平平無奇(此處有古天樂的臉),但在同一個包下,發現了另外一個註解,@MappedJdbcTypes,這不就是觸發jdbcType用的嗎,我激動了,把這個註解加到了NullToEmptyStringTypeHandler上面:
@MappedTypes(value = String.class)
@MappedJdbcTypes(value=JdbcType.VARCHAR)
public class NullToEmptyStringTypeHandler implements TypeHandler<String> {
......
把註解的sql和xml的sql的jdbcType加上去,把手寫的typeHandler去掉,結果是MyUserMapper(註解方式)和MyTaskMapper(xml方式)都能夠觸發。自此,此案告破。
4、結案陳詞
在mybatis中,需要自定義控制欄位的轉換,可以自己實現TypeHandler<T>介面,這樣在執行sql語句的時候,就會自動觸發TypeHandler的實現類,實TypeHandler的實現類有兩個註解,@MappedTypes和@MappedJdbcTypes,註解的規則如下:
- 這兩個註解是觸發TypeHandler的條件,MappedTypes是輸入欄位的java型別,比如String,Integer等
- MappedJdbcTypes是資料欄位的資料庫型別,比如VARCHR,INT等,但是這個欄位型別需要自行在mybatis的sql裡面自行配置,mybatis並不會自己從資料庫讀取。
- 這兩個條件取的是並集關係,如果配置了MappedTypes和MappedJdbcTypes,必須符合這兩者的條件才會觸發TypeHandler實現類。
- 如果在欄位的配置上面寫明瞭typeHandler=TypeHandler實現類,那麼就會無視上面註解的條件,觸發該TypeHandler實現類
5、案情擴充套件
案情雖然告破,但涉案人員(開始的那位提問題的同事)不樂意了,表示xml檔案的還好辦,可以用mybatis generator來搞定(mybatis generator後續會有專門的教程,先挖個坑),但如果是用註解,並不想每個欄位都標記jdbcType,那怎麼搞?其實有個辦法的,看程式碼:
@MappedTypes(value = MyUser.class)
public class MyUserTypeHandler implements TypeHandler<MyUser> {
@Override
public void setParameter(PreparedStatement ps, int i, MyUser parameter, JdbcType jdbcType) throws SQLException {
System.out.println("into MyUserTypeHandler,parameter="+parameter+",jdbcType="+jdbcType);
}
......
}
MappedTypes可不只是可以傳String,Integer這些單欄位的型別,可以直接報物件的型別傳進來,這樣,每個物件屬性都會觸發TypeHandler實現類,這樣,就不需要每個欄位都標記jdbcType了,而可以根據物件屬性的java型別自行判斷後去處理。
好了,到此為止,全案完結,需要閱讀完整卷宗的,請自行取閱,
1、案件背景
前天,一個涉案人員(同事)提到,在mysql的資料庫中,dba推薦的做法是所有的varchar欄位都設定成不能為
編輯手記:注重細節,是DBA必要的基本素質要求。
上一篇文章討論了非空欄位中如果存在空值對於查詢的影響,這裡描述一下導致問題的原因。
書接上文(參考:空與非空 – 資料庫中也有薛定諤的貓?),其實CBO的判斷本身是沒有問題的,問題在於,為什麼一個空值會存在非空約束的欄位中。
SQL> sel Youtube視訊
https://www.youtube.com/watch?v=TWpyhsPAK14
幾個基本假設
粒子的運動軌跡由波動函式\(\psi(x,t)\)完全描述,其中\(|\psi(x,t)|^2\)表示\(t\)時刻,粒子出現在\(x\)的概率
任意一種測量都對應一個
標題 薛定諤把妹法
“薛定諤把妹法”其靈感來自著名的物理學假設“薛定諤的貓”。“薛定諤把妹法”中心思想是:事件在被觀察以前,一直處在一個所謂“概率雲”的狀態下,一旦受到觀察,則坍縮為實體。通俗一些,就是要給女生神祕感。 中文名 薛定諤把妹法 外文名 Chase girls with Sc “90後”女大學生放棄北大保送復旦!她到底有多厲害? 東北網12月6日訊(記者 姜姍姍) 在東北農業大學有這樣一個自強不息的女大學生,她放棄北大直博被保送到復旦大學藥學院。她本科期間獲得國家獎學金、國家勵志獎學金、新東方自強獎學金、第一屆全國大學生生命聯賽國家二等獎……被評為黑龍江省“三好學生”。她就是生命
定態薛定諤方程是
如果是定態的自由粒子,這個方程的解是
因為是定態的波函式與時間無關,這個粒子的能量E不隨時間變化
假設E=1,讓t→0
所以波函式變成
讓A和都等於1
讓神經網路裡的節點都是在位形空間中
function h1 = main_gui_export()
% This is the machine-generated representation of a Handle Graphics object
% and its children. Note that handle values may
喜歡物理學尤其是量子力學的朋友一定對薛定諤的貓不會陌生,至於那些不大懂的小夥伴建議可以網上搜索瞭解下,對你的人生觀、價值觀可能會有所改變(不說笑,真的哦)。
對於量子論從愛因斯坦、波爾時代至今,一直是人們討論的焦點。因為量子論只存在於理論和思維實驗,理論上
物理學是一門研究物質運動最一般規律和物質基本結構的學科。分為純物理學和多學科物理學,其中的純物理
本文原創並首發於公眾號【Python貓】,未經授權,請勿轉載。
原文地址:https://mp.weixin.qq.com/s/
12月6日至12日,粉絲可在“和霍格沃茨之謎一起過聖誕”活動中為最喜愛的假日裝飾投票
--(美國商業資訊)--Jam City:
人物:
TypeHandler簡介
在MyBatis中,StatementHandler負責對需要執行的SQL語句進行預編譯處理,主要完成以下兩項工作:1.呼叫引數處理器(ParameterHandler)來設定需要傳入SQL的引數;2.呼叫結果集處理器(ResultSetHand
概述
在專案開發過程中經常會遇到資料庫儲存的是數值,在Java程式碼列舉表示的欄位。這些欄位在儲存和查詢時需要做一個轉換:寫資料庫的時候將列舉轉換為數字,讀資料庫時將數字轉換為列舉。
下面介紹一種通過mybatis註解實現資料型別自動轉換的方式。該方式能處理 是否 tle dsta ref 截器 throws dev ndt pri
1 package com.development;
2
3 import java.lang.reflect.InvocationTargetException;
4 impo 建表 pan schemardd 特性 -s map data div popu
背景
spark-shell是一個scala編程解釋運行環境,能夠通過編程的方式處理邏輯復雜的計算,但對於簡單的類似sql的數據處理,比方分組求和,sql為”selec urn 測試類 new post 綁定 def 情況下 asstream implement 這裏是最基本的搭建:http://www.cnblogs.com/xuyiqing/p/8600888.html
接下來做到了簡單的增刪改查:http://www.cnblogs. thinkphp 緩存 實錄:我被緩存TP的緩存文件坑了一晚上!2018年3月21日晚上9點左右。自己開發的項目,為了優化系統性能,我盡量在數據查詢時添加緩存,如:M(‘tbale_name’) -> cache(‘cache_name’) -> select();為了讓緩存數據與最新數據 記不清 室友 記不清了 int class 界面 clas overflow over 最近準備面試,很想拓展下自己的技術棧,於是就找上了linux。 然後安裝linux當然用的是自己的sp3啦,反正平時也就當上網本用用,終於可以發掘一下潛力了233 然而夢想是美好 cef 協議 設置 object buffer nod 疑問 示例 HR 回顧TCP粘包/拆包問題解決方案
上文詳細說了TCP粘包/拆包問題產生的原因及解決方式,並以LineBasedFrameDecoder為例演示了粘包/拆包問題的實際解決方案,本文再介紹兩種粘包/拆包問 相關推薦
mybatis偵探實錄:typehandler薛定諤之謎
書接上文:薛定諤的貓是如何誕生的?
MIT量子力學公開課第6講:薛定諤方程筆記
薛定諤的貓,把妹法。用科學的辦法把妹,解決程式設計師終身大事
薛定諤的貓跳進了生物學界 化學家:沒有我可能辦不到
神經網路與定態薛定諤方程
Matlab薛定諤方程工具箱系列——GUI初步美化
從“薛定諤的貓”聯想到“好奇害死貓”
物理學四大神獸,除了“薛定諤的貓”, 你還知道哪幾個?
當Python中混進一隻薛定諤的貓……
《哈利·波特:霍格沃茨之謎》邀請玩家為魔法世界的聖誕節裝飾大廳
MyBatis初窺:自定義TypeHandler處理列舉
Java程式設計基礎:在Mybatis註解中使用typeHandler實現Java列舉與資料庫int值的自動轉換
MyBatis攔截器:給參數對象屬性賦值
spark定制之五:使用說明
Mybatis框架三:DAO層開發、Mapper動態代理開發
實錄:我被緩存TP的緩存文件坑了一晚上!
surface安裝linux采坑實錄:grub 無法安裝
Netty3:分隔符和定長解碼器