mybatis sql 语句构建器——迹忆客-ag捕鱼王app官网
java 程序员面对的最痛苦的事情之一就是在 java 代码中嵌入 sql 语句。这通常是因为需要动态生成 sql 语句,不然我们可以将它们放到外部文件或者存储过程中。如你所见,mybatis 在 xml 映射中具备强大的 sql 动态生成能力。但有时,我们还是需要在 java 代码里构建 sql 语句。此时,mybatis 有另外一个特性可以帮到你,让你从处理典型问题中解放出来,比如加号、引号、换行、格式化问题、嵌入条件的逗号管理及 and 连接。确实,在 java 代码中动态生成 sql 代码真的就是一场噩梦。例如:
string sql = "select p.id, p.username, p.password, p.full_name, "
"p.last_name,p.created_on, p.updated_on "
"from person p, account a "
"inner join department d on d.id = p.department_id "
"inner join company c on d.company_id = c.id "
"where (p.id = a.id and p.first_name like ?) "
"or (p.last_name like ?) "
"group by p.id "
"having (p.last_name like ?) "
"or (p.first_name like ?) "
"order by p.id, p.full_name";
ag捕鱼王app官网的解决方案
mybatis 3 提供了方便的工具类来帮助解决此问题。借助 sql 类,我们只需要简单地创建一个实例,并调用它的方法即可生成 sql 语句。让我们来用 sql 类重写上面的例子:
private string selectpersonsql() {
return new sql() {{
select("p.id, p.username, p.password, p.full_name");
select("p.last_name, p.created_on, p.updated_on");
from("person p");
from("account a");
inner_join("department d on d.id = p.department_id");
inner_join("company c on d.company_id = c.id");
where("p.id = a.id");
where("p.first_name like ?");
or();
where("p.last_name like ?");
group_by("p.id");
having("p.last_name like ?");
or();
having("p.first_name like ?");
order_by("p.id");
order_by("p.full_name");
}}.tostring();
}
这个例子有什么特别之处吗?仔细看一下你会发现,你不用担心可能会重复出现的 "and" 关键字,或者要做出用 "where" 拼接还是 "and" 拼接还是不用拼接的选择。sql 类已经为你处理了哪里应该插入 "where"、哪里应该使用 "and" 的问题,并帮你完成所有的字符串拼接工作。
sql 类
这里有一些示例:
// 匿名内部类风格
public string deletepersonsql() {
return new sql() {{
delete_from("person");
where("id = #{id}");
}}.tostring();
}
// builder / fluent 风格
public string insertpersonsql() {
string sql = new sql()
.insert_into("person")
.values("id, first_name", "#{id}, #{firstname}")
.values("last_name", "#{lastname}")
.tostring();
return sql;
}
// 动态条件(注意参数需要使用 final 修饰,以便匿名内部类对它们进行访问)
public string selectpersonlike(final string id, final string firstname, final string lastname) {
return new sql() {{
select("p.id, p.username, p.password, p.first_name, p.last_name");
from("person p");
if (id != null) {
where("p.id like #{id}");
}
if (firstname != null) {
where("p.first_name like #{firstname}");
}
if (lastname != null) {
where("p.last_name like #{lastname}");
}
order_by("p.last_name");
}}.tostring();
}
public string deletepersonsql() {
return new sql() {{
delete_from("person");
where("id = #{id}");
}}.tostring();
}
public string insertpersonsql() {
return new sql() {{
insert_into("person");
values("id, first_name", "#{id}, #{firstname}");
values("last_name", "#{lastname}");
}}.tostring();
}
public string updatepersonsql() {
return new sql() {{
update("person");
set("first_name = #{firstname}");
where("id = #{id}");
}}.tostring();
}
方法 | 描述 |
---|---|
|
开始新的或追加到已有的 select子句。可以被多次调用,参数会被追加到 select 子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意参数。 |
|
开始新的或追加到已有的 select子句,并添加 distinct 关键字到生成的查询中。可以被多次调用,参数会被追加到 select 子句。 参数通常使用逗号分隔的列名和别名列表,但也可以是数据库驱动程序接受的任意参数。 |
|
开始新的或追加到已有的 from子句。可以被多次调用,参数会被追加到 from子句。 参数通常是一个表名或别名,也可以是数据库驱动程序接受的任意参数。 |
|
基于调用的方法,添加新的合适类型的 join 子句。 参数可以包含一个由列和连接条件构成的标准连接。 |
|
插入新的 where 子句条件,并使用 and 拼接。可以被多次调用,对于每一次调用产生的新条件,会使用 and 拼接起来。要使用 or 分隔,请使用 or()。 |
or() | 使用 or 来分隔当前的 where 子句条件。 可以被多次调用,但在一行中多次调用会生成错误的 sql。 |
and() | 使用 and 来分隔当前的 where子句条件。 可以被多次调用,但在一行中多次调用会生成错误的 sql。由于 where 和 having都会自动使用 and 拼接, 因此这个方法并不常用,只是为了完整性才被定义出来。 |
|
追加新的 group by 子句,使用逗号拼接。可以被多次调用,每次调用都会使用逗号将新的条件拼接起来。 |
|
追加新的 having 子句。使用 and 拼接。可以被多次调用,每次调用都使用and来拼接新的条件。要使用 or 分隔,请使用 or()。 |
|
追加新的 order by 子句,使用逗号拼接。可以多次被调用,每次调用会使用逗号拼接新的条件。 |
|
追加新的 limit 子句。 仅在 select()、update()、delete() 时有效。 当在 select() 中使用时,应该配合 offset() 使用。(于 3.5.2 引入) |
|
追加新的 offset 子句。 仅在 select() 时有效。 当在 select() 时使用时,应该配合 limit() 使用。(于 3.5.2 引入) |
|
追加新的 offset n rows 子句。 仅在 select() 时有效。 该方法应该配合 fetch_first_rows_only() 使用。(于 3.5.2 加入) |
|
追加新的 fetch first n rows only 子句。 仅在 select() 时有效。 该方法应该配合 offset_rows() 使用。(于 3.5.2 加入) |
delete_from(string) | 开始新的 delete 语句,并指定删除表的表名。通常它后面都会跟着一个 where 子句! |
insert_into(string) | 开始新的 insert 语句,并指定插入数据表的表名。后面应该会跟着一个或多个 values() 调用,或 into_columns() 和 into_values() 调用。 |
|
对 update 语句追加 "set" 属性的列表 |
update(string) | 开始新的 update 语句,并指定更新表的表名。后面都会跟着一个或多个 set() 调用,通常也会有一个 where() 调用。 |
values(string, string) | 追加数据值到 insert 语句中。第一个参数是数据插入的列名,第二个参数则是数据值。 |
into_columns(string...) | 追加插入列子句到 insert 语句中。应与 into_values() 一同使用。 |
into_values(string...) | 追加插入值子句到 insert 语句中。应与 into_columns() 一同使用。 |
add_row() | 添加新的一行数据,以便执行批量插入。(于 3.5.2 引入) |
提示
注意,sql 类将原样插入 limit、offset、offset n rows 以及 fetch first n rows only 子句。换句话说,类库不会为不支持这些子句的数据库执行任何转换。 因此,用户应该要了解目标数据库是否支持这些子句。如果目标数据库不支持这些子句,产生的 sql 可能会引起运行错误。
从版本 3.4.2 开始,你可以像下面这样使用可变长度参数:
public string selectpersonsql() {
return new sql()
.select("p.id", "a.username", "a.password", "p.full_name", "d.department_name", "c.company_name")
.from("person p", "account a")
.inner_join("department d on d.id = p.department_id", "company c on d.company_id = c.id")
.where("p.id = a.id", "p.full_name like #{name}")
.order_by("p.id", "p.full_name")
.tostring();
}
public string insertpersonsql() {
return new sql()
.insert_into("person")
.into_columns("id", "full_name")
.into_values("#{id}", "#{fullname}")
.tostring();
}
public string updatepersonsql() {
return new sql()
.update("person")
.set("full_name = #{fullname}", "date_of_birth = #{dateofbirth}")
.where("id = #{id}")
.tostring();
}
从版本 3.5.2 开始,你可以像下面这样构建批量插入语句:
public string insertpersonssql() {
// insert into person (id, full_name)
// values (#{mainperson.id}, #{mainperson.fullname}) , (#{subperson.id}, #{subperson.fullname})
return new sql()
.insert_into("person")
.into_columns("id", "full_name")
.into_values("#{mainperson.id}", "#{mainperson.fullname}")
.add_row()
.into_values("#{subperson.id}", "#{subperson.fullname}")
.tostring();
}
从版本 3.5.2 开始,你可以像下面这样构建限制返回结果数的 select 语句,:
public string selectpersonswithoffsetlimitsql() {
// select id, name from person
// limit #{limit} offset #{offset}
return new sql()
.select("id", "name")
.from("person")
.limit("#{limit}")
.offset("#{offset}")
.tostring();
}
public string selectpersonswithfetchfirstsql() {
// select id, name from person
// offset #{offset} rows fetch first #{limit} rows only
return new sql()
.select("id", "name")
.from("person")
.offset_rows("#{offset}")
.fetch_first_rows_only("#{limit}")
.tostring();
}
sqlbuilder 和 selectbuilder (已经废弃)
在版本 3.2 之前,我们的实现方式不太一样,我们利用 threadlocal 变量来掩盖一些对 java dsl 不太友好的语言限制。现在,现代 sql 构建框架使用的构建器和匿名内部类思想已被人们所熟知。因此,我们废弃了基于这种实现方式的 selectbuilder 和 sqlbuilder 类。
下面的方法仅仅适用于废弃的 sqlbuilder 和 selectbuilder 类。
方法 | 描述 |
---|---|
begin() / reset() | 这些方法清空 selectbuilder 类的 threadlocal 状态,并准备好构建一个新的语句。开始新的语句时,begin() 是最名副其实的(可读性最好的)。但如果由于一些原因(比如程序逻辑在某些条件下需要一个完全不同的语句),在执行过程中要重置语句构建状态,就很适合使用 reset()。 |
sql() | 该方法返回生成的 sql() 并重置 selectbuilder 状态(等价于调用了 begin() 或 reset())。因此,该方法只能被调用一次! |
selectbuilder 和 sqlbuilder 类并不神奇,但最好还是知道它们的工作原理。 selectbuilder 以及 sqlbuilder 借助静态导入和 threadlocal 变量实现了对插入条件友好的简洁语法。要使用它们,只需要静态导入这个类的方法即可,就像这样(只能使用其中的一条,不能同时使用):
import static org.apache.ibatis.jdbc.selectbuilder.*;
import static org.apache.ibatis.jdbc.sqlbuilder.*;
然后就可以像下面这样创建一些方法:
/* 已被废弃 */
public string selectblogssql() {
begin(); // 重置 threadlocal 状态变量
select("*");
from("blog");
return sql();
}
/* 已被废弃 */
private string selectpersonsql() {
begin(); // 重置 threadlocal 状态变量
select("p.id, p.username, p.password, p.full_name");
select("p.last_name, p.created_on, p.updated_on");
from("person p");
from("account a");
inner_join("department d on d.id = p.department_id");
inner_join("company c on d.company_id = c.id");
where("p.id = a.id");
where("p.first_name like ?");
or();
where("p.last_name like ?");
group_by("p.id");
having("p.last_name like ?");
or();
having("p.first_name like ?");
order_by("p.id");
order_by("p.full_name");
return sql();
}