可能有些人也有過相似需求,通常都會選擇使用其餘的方式如Spring-JDBC等方式解決。java
可否經過MyBatis實現這樣的功能呢?git
爲了讓通用Mapper更完全的支持多表操做以及更靈活的操做,在<b>2.2.0版本</b>增長了一個能夠直接執行SQL的新類SqlMapper
。github
注:3.3.0版本去掉了這個類,這個類如今在EntityMapper項目sql
經過這篇博客,咱們來了解一下SqlMapper
。緩存
##SqlMapper
提供的方法app
SqlMapper
提供瞭如下這些公共方法:測試
Map<String,Object> selectOne(String sql)
ui
Map<String,Object> selectOne(String sql, Object value)
.net
<T> T selectOne(String sql, Class<T> resultType)
prototype
<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}
形式的參數,這些參數須要經過入參來傳值。須要的參數過多的時候,參數可使用Map
類型。另外這種狀況下的sql還支持下面這種複雜形式:
String sql = "<script>select * from sys_user where 1=1" + "<if test=\"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 //建立sqlMapper SqlMapper sqlMapper = new SqlMapper(sqlSession);
若是使用的Spring,那麼能夠按照下面的方式配置<bean>
:
<bean id="sqlMapper" class="com.github.abel533.sql.SqlMapper" scope="prototype"> <constructor-arg ref="sqlSession"/> </bean>
在Service中使用的時候能夠直接使用@Autowired
注入。
##簡單例子
在src/test/java
目錄的com.github.abel533.sql
包中包含這些方法的測試。
下面挑幾個看看如何使用。
###selectList
//查詢,返回List<Map> 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);
###注意
經過上面這些例子應該能對此有個基本的瞭解,可是若是你使用參數方式,建議閱讀下面的文章:
##實現原理
2015-03-09:最初想要設計這個功能的時候,感受會很複雜,想的也複雜,須要不少個類,所以當時沒有實現。
2015-03-10:突發奇想,設計瞭如今的這種方式。而且有種強烈的感受就是幸虧昨天沒有嘗試去實現,由於昨天晚上思考這個問題的時候是晚上10點多,而今天(10號)是晚上7點開始思考。我很慶幸在一個更清醒的狀態下去寫這段代碼。
下面簡單說思路和實現方式。
在寫MyBatis分頁插件的時候熟悉了MappedStatement
類。
在寫通用Mapper的時候熟悉了xml
轉SqlNode
結構。
若是我根據SQL動態的建立一個MappedStatement
,而後使用MappedStatement
的id
在sqlSession
中執行不就能夠了嗎?
想到這一點,一切就簡單了。
看看下面select查詢建立MappedStatement
的代碼:
/** * 建立一個查詢的MS * * @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
的源碼。