View Javadoc
1   /*
2    *    Copyright 2009-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
15   */
16  package org.apache.ibatis.submitted.sqlprovider;
17  
18  import java.lang.reflect.Field;
19  import java.lang.reflect.Method;
20  import java.lang.reflect.ParameterizedType;
21  import java.lang.reflect.Type;
22  import java.util.LinkedHashMap;
23  import java.util.List;
24  import java.util.Map;
25  
26  import org.apache.ibatis.annotations.Param;
27  import org.apache.ibatis.builder.annotation.ProviderContext;
28  import org.apache.ibatis.jdbc.SQL;
29  
30  public class OurSqlBuilder {
31  
32    public String buildGetUsersQuery(Map<String, Object> parameter) {
33      // MyBatis wraps a single List parameter in a Map with the key="list",
34      // so need to pull it out
35      @SuppressWarnings("unchecked")
36      List<Integer> ids = (List<Integer>) parameter.get("list");
37      StringBuilder sb = new StringBuilder();
38      sb.append("select * from users where id in (");
39      for (int i = 0; i < ids.size(); i++) {
40        if (i > 0) {
41          sb.append(",");
42        }
43        sb.append("#{list[");
44        sb.append(i);
45        sb.append("]}");
46      }
47      sb.append(") order by id");
48      return sb.toString();
49    }
50  
51    public String buildGetUserQuery(Number parameter) {
52      // parameter is not a single List or Array,
53      // so it is passed as is from the mapper
54      return "select * from users where id = #{value}";
55    }
56  
57    public String buildGetAllUsersQuery() {
58      return "select * from users order by id";
59    }
60  
61    public String buildGetUsersByCriteriaQuery(final User criteria) {
62      return new SQL() {
63        {
64          SELECT("*");
65          FROM("users");
66          if (criteria.getId() != null) {
67            WHERE("id = #{id}");
68          }
69          if (criteria.getName() != null) {
70            WHERE("name like #{name} || '%'");
71          }
72        }
73      }.toString();
74    }
75  
76    public String buildGetUsersByCriteriaMapQuery(final Map<String, Object> criteria) {
77      return new SQL() {
78        {
79          SELECT("*");
80          FROM("users");
81          if (criteria.get("id") != null) {
82            WHERE("id = #{id}");
83          }
84          if (criteria.get("name") != null) {
85            WHERE("name like #{name} || '%'");
86          }
87        }
88      }.toString();
89    }
90  
91    public String buildGetUsersByCriteriaMapWithParamQuery(@Param("id") Integer id, @Param("name") String name) {
92      return new SQL() {
93        {
94          SELECT("*");
95          FROM("users");
96          if (id != null) {
97            WHERE("id = #{id}");
98          }
99          if (name != null) {
100           WHERE("name like #{name} || '%'");
101         }
102       }
103     }.toString();
104   }
105 
106   public String buildGetUsersByNameQuery(final String name, final String orderByColumn) {
107     return new SQL() {
108       {
109         SELECT("*");
110         FROM("users");
111         if (name != null) {
112           WHERE("name like #{param1} || '%'");
113         }
114         ORDER_BY(orderByColumn);
115       }
116     }.toString();
117   }
118 
119   public String buildGetUsersByNameUsingMap(Map<String, Object> params) {
120     final String name = String.class.cast(params.get("param1"));
121     final String orderByColumn = String.class.cast(params.get("param2"));
122     return new SQL() {
123       {
124         SELECT("*");
125         FROM("users");
126         if (name != null) {
127           WHERE("name like #{param1} || '%'");
128         }
129         ORDER_BY(orderByColumn);
130       }
131     }.toString();
132   }
133 
134   public String buildGetUsersByNameWithParamNameAndOrderByQuery(@Param("orderByColumn") final String orderByColumn,
135       @Param("name") final String name) {
136     return new SQL() {
137       {
138         SELECT("*");
139         FROM("users");
140         if (name != null) {
141           WHERE("name like #{name} || '%'");
142         }
143         ORDER_BY(orderByColumn);
144       }
145     }.toString();
146   }
147 
148   public String buildGetUsersByNameWithParamNameQuery(@Param("name") final String name) {
149     return new SQL() {
150       {
151         SELECT("*");
152         FROM("users");
153         if (name != null) {
154           WHERE("name like #{name} || '%'");
155         }
156         ORDER_BY("id DESC");
157       }
158     }.toString();
159   }
160 
161   public String buildGetUsersByNameWithParamNameQueryUsingMap(Map<String, Object> params) {
162     final String name = String.class.cast(params.get("name"));
163     final String orderByColumn = String.class.cast(params.get("orderByColumn"));
164     return new SQL() {
165       {
166         SELECT("*");
167         FROM("users");
168         if (name != null) {
169           WHERE("name like #{param1} || '%'");
170         }
171         ORDER_BY(orderByColumn);
172       }
173     }.toString();
174   }
175 
176   public String buildInsert() {
177     return "insert into users (id, name) values (#{id}, #{name})";
178   }
179 
180   public String buildUpdate() {
181     return "update users set name = #{name} where id = #{id}";
182   }
183 
184   public String buildDelete() {
185     return "delete from users where id = #{id}";
186   }
187 
188   public String buildSelectByIdProviderContextOnly(ProviderContext context) {
189     final boolean containsLogicalDelete = context.getMapperMethod()
190         .getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
191     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
192     return new SQL() {
193       {
194         SELECT("*");
195         FROM(tableName);
196         WHERE("id = #{id}");
197         if (!containsLogicalDelete) {
198           WHERE("logical_delete = ${Constants.LOGICAL_DELETE_OFF}");
199         }
200       }
201     }.toString();
202   }
203 
204   public String buildSelectByNameOneParamAndProviderContext(ProviderContext context, final String name) {
205     final boolean containsLogicalDelete = context.getMapperMethod()
206         .getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
207     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
208     return new SQL() {
209       {
210         SELECT("*");
211         FROM(tableName);
212         if (name != null) {
213           WHERE("name like #{name} || '%'");
214         }
215         if (!containsLogicalDelete) {
216           WHERE("logical_delete = ${LOGICAL_DELETE_OFF:0}");
217         }
218       }
219     }.toString();
220   }
221 
222   public String buildSelectByIdAndNameMultipleParamAndProviderContextWithAtParam(@Param("id") final Integer id,
223       ProviderContext context, @Param("name") final String name) {
224     final boolean containsLogicalDelete = context.getMapperMethod()
225         .getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
226     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
227     return new SQL() {
228       {
229         SELECT("*");
230         FROM(tableName);
231         if (id != null) {
232           WHERE("id = #{id}");
233         }
234         if (name != null) {
235           WHERE("name like #{name} || '%'");
236         }
237         if (!containsLogicalDelete) {
238           WHERE("logical_delete = false");
239         }
240       }
241     }.toString();
242   }
243 
244   public String buildSelectByIdAndNameMultipleParamAndProviderContext(final Integer id, final String name,
245       ProviderContext context) {
246     final boolean containsLogicalDelete = context.getMapperMethod()
247         .getAnnotation(BaseMapper.ContainsLogicalDelete.class) != null;
248     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
249     return new SQL() {
250       {
251         SELECT("*");
252         FROM(tableName);
253         if (id != null) {
254           WHERE("id = #{param1}");
255         }
256         if (name != null) {
257           WHERE("name like #{param2} || '%'");
258         }
259         if (!containsLogicalDelete) {
260           WHERE("logical_delete = false");
261         }
262       }
263     }.toString();
264   }
265 
266   private Class<?> getEntityClass(ProviderContext providerContext) {
267     Method mapperMethod = providerContext.getMapperMethod();
268     Class<?> declaringClass = mapperMethod.getDeclaringClass();
269     Class<?> mapperClass = providerContext.getMapperType();
270 
271     Type[] types = mapperClass.getGenericInterfaces();
272     for (Type type : types) {
273       if (type instanceof ParameterizedType) {
274         ParameterizedType t = (ParameterizedType) type;
275         if (t.getRawType() == declaringClass || mapperClass.isAssignableFrom((Class<?>) t.getRawType())) {
276           return (Class<?>) t.getActualTypeArguments()[0];
277         }
278       }
279     }
280     throw new RuntimeException("The interface [" + mapperClass.getCanonicalName() + "] must specify a generic type.");
281   }
282 
283   private Map<String, String> getColumnMap(ProviderContext context) {
284     Class<?> entityClass = getEntityClass(context);
285     Field[] fields = entityClass.getDeclaredFields();
286     Map<String, String> columnMap = new LinkedHashMap<>();
287     for (Field field : fields) {
288       BaseMapper.Column column = field.getAnnotation(BaseMapper.Column.class);
289       if (column != null) {
290         String columnName = column.value();
291         if (columnName == null || columnName.length() == 0) {
292           columnName = field.getName();
293         }
294         columnMap.put(columnName, field.getName());
295       }
296     }
297     if (columnMap.size() == 0) {
298       throw new RuntimeException("There is no field in the class [" + entityClass.getCanonicalName()
299           + "] that specifies the @BaseMapper.Column annotation.");
300     }
301     return columnMap;
302   }
303 
304   public String buildInsertSelective(ProviderContext context) {
305     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
306     Map<String, String> columnMap = getColumnMap(context);
307     StringBuilder sqlBuffer = new StringBuilder();
308     sqlBuffer.append("<script>");
309     sqlBuffer.append("insert into ");
310     sqlBuffer.append(tableName);
311     sqlBuffer.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
312     for (Map.Entry<String, String> entry : columnMap.entrySet()) {
313       sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
314       sqlBuffer.append(entry.getKey()).append(",");
315       sqlBuffer.append("</if>");
316     }
317     sqlBuffer.append("</trim>");
318     sqlBuffer.append("<trim prefix=\"VALUES (\" suffix=\")\" suffixOverrides=\",\">");
319     for (String field : columnMap.values()) {
320       sqlBuffer.append("<if test=\"").append(field).append(" != null\">");
321       sqlBuffer.append("#{").append(field).append("} ,");
322       sqlBuffer.append("</if>");
323     }
324     sqlBuffer.append("</trim>");
325     sqlBuffer.append("</script>");
326     return sqlBuffer.toString();
327   }
328 
329   public String buildUpdateSelective(ProviderContext context) {
330     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
331     Map<String, String> columnMap = getColumnMap(context);
332     StringBuilder sqlBuffer = new StringBuilder();
333     sqlBuffer.append("<script>");
334     sqlBuffer.append("update ");
335     sqlBuffer.append(tableName);
336     sqlBuffer.append("<set>");
337     for (Map.Entry<String, String> entry : columnMap.entrySet()) {
338       sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
339       sqlBuffer.append(entry.getKey()).append(" = #{").append(entry.getValue()).append("} ,");
340       sqlBuffer.append("</if>");
341     }
342     sqlBuffer.append("</set>");
343     // For simplicity, there is no @Id annotation here, using default id directly
344     sqlBuffer.append("where id = #{id}");
345     sqlBuffer.append("</script>");
346     return sqlBuffer.toString();
347   }
348 
349   public String buildGetByEntityQuery(ProviderContext context) {
350     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
351     Map<String, String> columnMap = getColumnMap(context);
352     StringBuilder sqlBuffer = new StringBuilder();
353     sqlBuffer.append("<script>");
354     sqlBuffer.append("select * from ");
355     sqlBuffer.append(tableName);
356     sqlBuffer.append("<where>");
357     for (Map.Entry<String, String> entry : columnMap.entrySet()) {
358       sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
359       sqlBuffer.append("and ").append(entry.getKey()).append(" = #{").append(entry.getValue()).append("}");
360       sqlBuffer.append("</if>");
361     }
362     sqlBuffer.append("</where>");
363     sqlBuffer.append("</script>");
364     return sqlBuffer.toString();
365   }
366 
367 }