MyBatis直接執行SQL的工具SqlMapper
可能有些人也有過類似需求,一般都會選擇使用其他的方式如Spring-JDBC等方式解決。
能否通過MyBatis實現這樣的功能呢?
為了讓通用Mapper更徹底的支援多表操作以及更靈活的操作,在<b>2.2.0版本</b>增加了一個可以直接執行SQL的新類SqlMapper
。
注:3.3.0版本去掉了這個類,這個類現在在EntityMapper專案
通過這篇部落格,我們來了解一下SqlMapper
。
##SqlMapper
提供的方法
SqlMapper (官網:www.fhadmin.org)
提供了以下這些公共方法:
-
Map<String,Object> selectOne(String sql)
-
Map<String,Object> selectOne(String sql, Object value)
-
<T> T selectOne(String sql, Class<T> resultType)
-
<T> T selectOne(String sql, Object value, Class<T> resultType)
-
List<Map<String,Object>> selectList(String sql)
-
List<Map<String,Object>> selectList(String sql, Object value)
-
<T> List<T> selectList(String sql, Class<T> resultType)
-
<T> List<T> selectList(String sql, Object value, Class<T> resultType)
-
int insert(String sql)
-
int insert(String sql, Object value)
-
int update(String sql)
-
int update(String sql, Object value)
-
int delete(String sql)
-
int delete(String sql, Object value)
一共14個方法,這些方法的命名和引數和SqlSession
介面的很像,只是基本上第一個引數都成了sql。
其中Object value
為入參,入參形式和SqlSession
中的入參一樣,帶有入參的方法,在使用時sql可以包含#{param}
或${param}
形式的引數,這些引數需要通過入參來傳值。(官網:www.fhadmin.org) 需要的引數過多的時候,引數可以使用Map
型別。另外這種情況下的sql還支援下面這種複雜形式:
String sql = "<script>select * from sys_user where 1=1" +
"<iftest=\"usertype != null\">usertype = #{usertype}</if></script>";
這種情況用的比較少,不多說。
不帶有Object value
的所有方法,sql中如果有引數需要手動拼接成一個可以直接執行的sql語句。
在selectXXX
方法中,使用Class<T> resultType
可以指定返回型別,否則就是Map<String,Object>
型別。
##例項化SqlMapper
SqlMapper
構造引數public SqlMapper(SqlSession sqlSession)
,需要一個入參SqlSession sqlSession
,在一般系統中,可以按照下面的方式獲取:
SqlSession sqlSession = (...);//通過某些方法獲取sqlSession (官網:www.fhadmin.org)
//建立sqlMapper
SqlMapper sqlMapper = new SqlMapper(sqlSession);
如果使用的Spring,那麼可以按照下面的方式配置<bean>
:
<bean id="sqlMapper" class="com.github.abel533.sql.SqlMapper" scope="prototype">
<constructor-argref="sqlSession"/></bean>
在Service中使用的時候可以直接使用@Autowired
注入。
##簡單例子
在src/test/java
目錄的com.github.abel533.sql
包中包含這些方法的測試。
下面挑幾個看看如何使用。
###selectList
//查詢,返回List<Map> (官網:www.fhadmin.org)
List<Map<String, Object>> list = sqlMapper.selectList("select * from country where id < 11");
//查詢,返回指定的實體類
List<Country> countryList = sqlMapper.selectList("select * from country where id < 11", Country.class);
//查詢,帶引數
countryList = sqlMapper.selectList("select * from country where id < #{id}", 11, Country.class);
//複雜點的查詢,這裡引數和上面不同的地方,在於傳入了一個物件
Country country = new Country();
country.setId(11);
countryList = sqlMapper.selectList("<script>" +
"select * from country " +
" <where>" +
" <if test=\"id != null\">" +
" id < #{id}" +
" </if>" +
" </where>" +
"</script>", country, Country.class);
###selectOne
Map<String, Object> map = sqlMapper.selectOne("select * from country where id = 35");
map = sqlMapper.selectOne("select * from country where id = #{id}", 35);
Country country = sqlMapper.selectOne("select * from country where id = 35", Country.class);
country = sqlMapper.selectOne("select * from country where id = #{id}", 35, Country.class);
###insert,update,delete
//insert
int result = sqlMapper.insert("insert into country values(1921,'天朝','TC')");
Country tc = new Country();
tc.setId(1921);
tc.setCountryname("天朝");
tc.setCountrycode("TC");
//注意這裡的countrycode和countryname故意寫反的
result = sqlMapper.insert("insert into country values(#{id},#{countrycode},#{countryname})"
, tc);
//update
result = sqlMapper.update("update country set countryname = '天朝' where id = 35");
tc = new Country();
tc.setId(35);
tc.setCountryname("天朝");
int result = sqlMapper.update("update country set countryname = #{countryname}" +
" where id in(select id from country where countryname like 'A%')", tc);
//delete
result = sqlMapper.delete("delete from country where id = 35");
result = sqlMapper.delete("delete from country where id = #{id}", 35);
###注意
通過上面這些例子應該能對此有個基本的瞭解,(官網:www.fhadmin.org) 但是如果你使用引數方式,建議閱讀下面的文章:
##實現原理
2015-03-09:最初想要設計這個功能的時候,感覺會很複雜,想的也複雜,需要很多個類,因此當時沒有實現。
2015-03-10:突發奇想,設計了現在的這種方式。並且有種強烈的感覺就是幸好昨天沒有嘗試去實現,因為昨天晚上思考這個問題的時候是晚上10點多,而今天(10號)是晚上7點開始思考。我很慶幸在一個更清醒的狀態下去寫這段程式碼。
下面簡單說思路和實現方式。
在寫MyBatis分頁外掛的時候熟悉了MappedStatement
類。
在寫通用Mapper的時候熟悉了xml
轉SqlNode
結構。
如果我根據SQL動態的建立一個MappedStatement
,然後使用MappedStatement
的id
在sqlSession
中執行不就可以了嗎?
想到這一點,一切就簡單了。
看看下面select查詢建立MappedStatement
的程式碼:
/**
* 建立一個查詢的MS(官網:www.fhadmin.org)
*
* @param msId
* @param sqlSource 執行的sqlSource
* @param resultType 返回的結果型別
*/
private void newSelectMappedStatement(String msId, SqlSource sqlSource, final Class<?> resultType) {
MappedStatement ms = new MappedStatement.Builder(
configuration, msId, sqlSource, SqlCommandType.SELECT)
.resultMaps(new ArrayList<ResultMap>() {
{
add(new ResultMap.Builder(configuration,
"defaultResultMap",
resultType,
new ArrayList<ResultMapping>(0)).build());
}
})
.build();
//快取
configuration.addMappedStatement(ms);
}
程式碼是不是很簡單,這段程式碼的關鍵是引數sqlSource
,下面是建立SqlSource
的方法,分為兩種。
一種是一個完整的sql,不需要引數的,可以直接執行的:
StaticSqlSource sqlSource = new StaticSqlSource(configuration, sql);
其中configuration
從sqlSession
中獲取,sql
就是使用者傳入到sql語句,是不是也很簡單?
另一種是支援動態sql的,支援引數的SqlSource
:
SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, parameterType);
是不是也很簡單?這個方法其實可以相容上面的StaticSqlSource
,這裡比上面多了一個parameterType
,因為這兒是可以傳遞引數的,另外languageDriver
是從configuration
中獲取的。
是不是很簡單?
我一開始也沒想到MyBatis直接執行sql實現起來會這麼的容易。
insert,delete,update
方法的建立更容易,因為他們的返回值都是int
,所以處理起來更簡單,有興趣的可以去通用Mapper下的包com.github.abel533.sql
中檢視SqlMapper
的原始碼。