View Javadoc
1   /*
2    *    Copyright 2009-2024 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 t) {
274         if (t.getRawType() == declaringClass || mapperClass.isAssignableFrom((Class<?>) t.getRawType())) {
275           return (Class<?>) t.getActualTypeArguments()[0];
276         }
277       }
278     }
279     throw new RuntimeException("The interface [" + mapperClass.getCanonicalName() + "] must specify a generic type.");
280   }
281 
282   private Map<String, String> getColumnMap(ProviderContext context) {
283     Class<?> entityClass = getEntityClass(context);
284     Field[] fields = entityClass.getDeclaredFields();
285     Map<String, String> columnMap = new LinkedHashMap<>();
286     for (Field field : fields) {
287       BaseMapper.Column column = field.getAnnotation(BaseMapper.Column.class);
288       if (column != null) {
289         String columnName = column.value();
290         if (columnName == null || columnName.length() == 0) {
291           columnName = field.getName();
292         }
293         columnMap.put(columnName, field.getName());
294       }
295     }
296     if (columnMap.size() == 0) {
297       throw new RuntimeException("There is no field in the class [" + entityClass.getCanonicalName()
298           + "] that specifies the @BaseMapper.Column annotation.");
299     }
300     return columnMap;
301   }
302 
303   public String buildInsertSelective(ProviderContext context) {
304     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
305     Map<String, String> columnMap = getColumnMap(context);
306     StringBuilder sqlBuffer = new StringBuilder();
307     sqlBuffer.append("<script>");
308     sqlBuffer.append("insert into ");
309     sqlBuffer.append(tableName);
310     sqlBuffer.append("<trim prefix=\"(\" suffix=\")\" suffixOverrides=\",\">");
311     for (Map.Entry<String, String> entry : columnMap.entrySet()) {
312       sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
313       sqlBuffer.append(entry.getKey()).append(",");
314       sqlBuffer.append("</if>");
315     }
316     sqlBuffer.append("</trim>");
317     sqlBuffer.append("<trim prefix=\"VALUES (\" suffix=\")\" suffixOverrides=\",\">");
318     for (String field : columnMap.values()) {
319       sqlBuffer.append("<if test=\"").append(field).append(" != null\">");
320       sqlBuffer.append("#{").append(field).append("} ,");
321       sqlBuffer.append("</if>");
322     }
323     sqlBuffer.append("</trim>");
324     sqlBuffer.append("</script>");
325     return sqlBuffer.toString();
326   }
327 
328   public String buildUpdateSelective(ProviderContext context) {
329     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
330     Map<String, String> columnMap = getColumnMap(context);
331     StringBuilder sqlBuffer = new StringBuilder();
332     sqlBuffer.append("<script>");
333     sqlBuffer.append("update ");
334     sqlBuffer.append(tableName);
335     sqlBuffer.append("<set>");
336     for (Map.Entry<String, String> entry : columnMap.entrySet()) {
337       sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
338       sqlBuffer.append(entry.getKey()).append(" = #{").append(entry.getValue()).append("} ,");
339       sqlBuffer.append("</if>");
340     }
341     sqlBuffer.append("</set>");
342     // For simplicity, there is no @Id annotation here, using default id directly
343     sqlBuffer.append("where id = #{id}");
344     sqlBuffer.append("</script>");
345     return sqlBuffer.toString();
346   }
347 
348   public String buildGetByEntityQuery(ProviderContext context) {
349     final String tableName = context.getMapperType().getAnnotation(BaseMapper.Meta.class).tableName();
350     Map<String, String> columnMap = getColumnMap(context);
351     StringBuilder sqlBuffer = new StringBuilder();
352     sqlBuffer.append("<script>");
353     sqlBuffer.append("select * from ");
354     sqlBuffer.append(tableName);
355     sqlBuffer.append("<where>");
356     for (Map.Entry<String, String> entry : columnMap.entrySet()) {
357       sqlBuffer.append("<if test=\"").append(entry.getValue()).append(" != null\">");
358       sqlBuffer.append("and ").append(entry.getKey()).append(" = #{").append(entry.getValue()).append("}");
359       sqlBuffer.append("</if>");
360     }
361     sqlBuffer.append("</where>");
362     sqlBuffer.append("</script>");
363     return sqlBuffer.toString();
364   }
365 
366 }