![](/img/trans.png)
[英]MyBatis how can I generate different sql for different database backend
[英]Can I use MyBatis to generate Dynamic SQL without executing it?
我有一些復雜的查詢要用一些可選的過濾器來構建,MyBatis 似乎是生成動態 SQL 的理想候選者。
但是,我仍然希望我的查詢在與應用程序(未使用 MyBatis)的 rest 相同的框架中執行。
所以我希望做的是嚴格使用 MyBatis 來生成 SQL,但是從那里使用我的應用程序的 rest 來實際執行它。 這可能嗎? 如果是這樣,如何?
雖然MyBatis設計用於在構建查詢后執行查詢,但您可以利用它的配置和一些“內部知識”來獲得所需內容。
MyBatis是一個非常好的框架,遺憾的是它缺少文檔方面,因此源代碼是你的朋友。 如果你四處挖掘,你應該碰到這些類: org.apache.ibatis.mapping.MappedStatement
和org.apache.ibatis.mapping.BoundSql
,它們是構建動態SQL的關鍵角色。 這是一個基本的用法示例:
具有此數據的MySQL表user
:
name login
----- -----
Andy a
Barry b
Cris c
User
類:
package pack.test;
public class User {
private String name;
private String login;
// getters and setters ommited
}
UserService
接口:
package pack.test;
public interface UserService {
// using a different sort of parameter to show some dynamic SQL
public User getUser(int loginNumber);
}
UserService.xml
映射文件:
<mapper namespace="pack.test.UserService">
<select id="getUser" resultType="pack.test.User" parameterType="int">
<!-- dynamic change of parameter from int index to login string -->
select * from user where login = <choose>
<when test="_parameter == 1">'a'</when>
<when test="_parameter == 2">'b'</when>
<otherwise>'c'</otherwise>
</choose>
</select>
</mapper>
sqlmap-config.file
:
<configuration>
<settings>
<setting name="lazyLoadingEnabled" value="false" />
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost/test"/>
<property name="username" value="..."/>
<property name="password" value="..."/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="pack/test/UserService.xml"/>
</mappers>
</configuration>
AppTester
顯示結果:
package pack.test;
import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
public class AppTester {
private static String CONFIGURATION_FILE = "sqlmap-config.xml";
public static void main(String[] args) throws Exception {
Reader reader = null;
SqlSession session = null;
try {
reader = Resources.getResourceAsReader(CONFIGURATION_FILE);
session = new SqlSessionFactoryBuilder().build(reader).openSession();
UserService userService = session.getMapper(UserService.class);
// three users retreived from index
for (int i = 1; i <= 3; i++) {
User user = userService.getUser(i);
System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());
// must mimic the internal statement key for the mapper and method you are calling
MappedStatement ms = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
BoundSql boundSql = ms.getBoundSql(i); // parameter for the SQL statement
System.out.println("SQL used: " + boundSql.getSql());
System.out.println();
}
} finally {
if (reader != null) {
reader.close();
}
if (session != null) {
session.close();
}
}
}
}
結果如下:
Retreived user: Andy a
SQL used: select * from user where login = 'a'
Retreived user: Barry b
SQL used: select * from user where login = 'b'
Retreived user: Cris c
SQL used: select * from user where login = 'c'
每個人都知道如何使用BoundSql.getSql()從MyBatis獲取一個參數化的查詢字符串,如下所示:
// get parameterized query
MappedStatement ms = configuration.getMappedStatement("MyMappedStatementId");
BoundSql boundSql = ms.getBoundSql(parameters);
System.out.println("SQL" + boundSql.getSql());
// SELECT species FROM animal WHERE name IN (?, ?) or id = ?
但是現在您需要方程的另一半,即與問號對應的值列表:
// get parameters
List<ParameterMapping> boundParams = boundSql.getParameterMappings();
String paramString = "";
for(ParameterMapping param : boundParams) {
paramString += boundSql.getAdditionalParameter(param.getProperty()) + ";";
}
System.out.println("params:" + paramString);
// "Spot;Fluffy;42;"
現在,您可以將其序列化以發送到其他位置以進行運行,或者您可以將其打印到日志中,以便將它們拼接在一起並手動運行查詢。
*未經測試的代碼,可能是次要類型問題等
public static void main(String[] args) throws Exception {
String script = "<script>select * from table where 1 = 1<if test='id != null'>and id = ${id} </if></script>";
System.out.println(buildSql(script));
}
private static String buildSql(String script) {
LanguageDriver languageDriver = new XMLLanguageDriver();
Configuration configuration = new Configuration();
SqlSource sqlSource = languageDriver.createSqlSource(configuration, script, Object.class);
Map<String, String> parameters = new HashMap<>();
parameters.put("id", "1");
BoundSql boundSql = sqlSource.getBoundSql(parameters);
return boundSql.getSql();
}
使用 ${id} 而不是 #{id}
結果是:select * 來自表,其中 1 = 1 且 id = 1
只是添加到Bogdan的正確答案:如果您的接口具有更復雜的簽名,則需要將JavaBean傳遞給getBoundSql()
其中getter用於接口參數。
假設您要根據登錄號和/或用戶名查詢用戶。 您的界面可能如下所示:
package pack.test;
public interface UserService {
// using a different sort of parameter to show some dynamic SQL
public User getUser(@Param("number") int loginNumber, @Param("name") String name);
}
我省略了Mapper代碼,因為它與此討論無關,但AppTester中的代碼應該變為:
[...]
final String name = "Andy";
User user = userService.getUser(i, name);
System.out.println("Retreived user: " + user.getName() + " " + user.getLogin());
// must mimic the internal statement key for the mapper and method you are calling
MappedStatement ms = session.getConfiguration().getMappedStatement(UserService.class.getName() + ".getUser");
BoundSql boundSql = ms.getBoundSql(new Object() {
// provide getters matching the @Param's in the interface declaration
public Object getNumber() {
return i;
}
public Object getName() {
return name;
}
});
System.out.println("SQL used: " + boundSql.getSql());
System.out.println();
[...]
mybatis版本是3.4.5
要將mapper轉換為sql,需要mapper接口類,方法名,參數和sqlSession。
package util;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import org.apache.ibatis.binding.MapperMethod.MethodSignature;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.springframework.util.CollectionUtils;
/**
* @author zwxbest - 19-4-25
*/
public class SqlUtil {
public static String showSql(SqlSession sqlSession, Class mapperInterface, String methodName,
Object[] params) {
Configuration configuration = sqlSession.getConfiguration();
MappedStatement ms = configuration.getMappedStatement(
mapperInterface.getName() + "." + methodName);
Method sqlMethod = null;
//find method equals methodName
for (Method method : mapperInterface.getDeclaredMethods()) {
if (method.getName().equals(methodName)) {
sqlMethod = method;
break;
}
}
if (sqlMethod == null) {
throw new RuntimeException("mapper method is not found");
}
MethodSignature method = new MethodSignature(configuration, mapperInterface, sqlMethod);
Object paramObject = method.convertArgsToSqlCommandParam(params);
BoundSql boundSql = ms.getBoundSql(paramObject);
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappings = boundSql
.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (!CollectionUtils.isEmpty(parameterMappings) && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration
.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(
parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else {
sql = sql.replaceFirst("\\?", "missing");
}
}
}
}
return sql;
}
/**
* if param's type is `String`,add single quotation<br>
*
* if param's type is `datetime`,convert to string and quote <br>
*/
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat
.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else if (obj instanceof LocalDateTime) {
value = "\'" + ((LocalDateTime) obj)
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) + "\'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
}
sqlSession由Spring注入。
@Autowired
private SqlSession sqlSession;
String sql = SqlUtil
.showSql(sqlSession, PromotionCodeMapper.class, "selectByPromotionCodeForUpdate",
new Object[]{"111"});
log.warn(sql);
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.