可能有些人也有过类似需求,一般都会选择使用其他的方式如spring-jdbc等方式解决。

能否通过mybatis实现这样的功能呢?

为了让通用mapper更彻底的支持多表操作以及更灵活的操作,在2.2.0版本增加了一个可以直接执行sql的新类sqlmapper。

我们来了解一下sqlmapper。

sqlmapper提供的方法
sqlmapper提供了以下这些公共方法:
map selectone(string sql)
map selectone(string sql, object value)
 t selectone(string sql, class resulttype)
 t selectone(string sql, object value, class resulttype)
list> selectlist(string sql)
list> selectlist(string sql, object value)
 list selectlist(string sql, class resulttype)
 list selectlist(string sql, object value, class 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 = "
"usertype = #{usertype}";
这种情况用的比较少,不多说。
不带有object value的所有方法,sql中如果有参数需要手动拼接成一个可以直接执行的sql语句。
在selectxxx方法中,使用class resulttype可以指定返回类型,否则就是map类型。
实例化sqlmapper
sqlmapper构造参数public sqlmapper(sqlsession sqlsession),需要一个入参sqlsession sqlsession,在一般系统中,可以按照下面的方式获取:
sqlsession sqlsession = (...);//通过某些方法获取sqlsession
//创建sqlmapper
sqlmapper sqlmapper = new sqlmapper(sqlsession);
如果使用的spring,那么可以按照下面的方式配置:
在service中使用的时候可以直接使用@autowired注入。
简单例子
在src/test/java目录的com.github.abel533.sql包中包含这些方法的测试。
下面挑几个看看如何使用。
selectlist
//查询,返回list
list> list = sqlmapper.selectlist("select * from country where id < 11");
//查询,返回指定的实体类
list 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("
"select * from country " +
" " +
" " +
" id < #{id}" +
" " +
" " +
"", country, country.class);
selectone
map 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);

注意

通过上面这些例子应该能对此有个基本的了解,但是如果你使用参数方式,建议阅读下面的文章:

实现原理

最初想要设计这个功能的时候,感觉会很复杂,想的也复杂,需要很多个类,因此当时没有实现。

突发奇想,设计了现在的这种方式。并且有种强烈的感觉就是幸好昨天没有尝试去实现,因为昨天晚上思考这个问题的时候是晚上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() {
{
add(new resultmap.builder(configuration,
"defaultresultmap",
resulttype,
new arraylist(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,所以处理起来更简单,有兴趣的可以查看sqlmapper的源码。