001/* 002 * Copyright (c) 2022-2023, Mybatis-Flex (fuhai999@gmail.com). 003 * <p> 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * <p> 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * <p> 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package com.mybatisflex.core.keygen; 017 018import com.mybatisflex.core.FlexConsts; 019import com.mybatisflex.core.util.MapUtil; 020import org.apache.ibatis.binding.MapperMethod.ParamMap; 021import org.apache.ibatis.executor.Executor; 022import org.apache.ibatis.executor.ExecutorException; 023import org.apache.ibatis.executor.keygen.KeyGenerator; 024import org.apache.ibatis.mapping.MappedStatement; 025import org.apache.ibatis.reflection.ArrayUtil; 026import org.apache.ibatis.reflection.MetaObject; 027import org.apache.ibatis.reflection.ParamNameResolver; 028import org.apache.ibatis.session.Configuration; 029import org.apache.ibatis.session.defaults.DefaultSqlSession.StrictMap; 030import org.apache.ibatis.type.JdbcType; 031import org.apache.ibatis.type.TypeHandler; 032import org.apache.ibatis.type.TypeHandlerRegistry; 033 034import java.sql.ResultSet; 035import java.sql.ResultSetMetaData; 036import java.sql.SQLException; 037import java.sql.Statement; 038import java.util.*; 039import java.util.Map.Entry; 040 041/** 042 * 主要作用是为 Row 生成自增主键 043 */ 044public class RowJdbc3KeyGenerator implements KeyGenerator { 045 046 private final String keyProperty; 047 048 049 private static final String SECOND_GENERIC_PARAM_NAME = ParamNameResolver.GENERIC_NAME_PREFIX + "2"; 050 private static final String MSG_TOO_MANY_KEYS = "Too many keys are generated. There are only %d target objects. " 051 + "You either specified a wrong 'keyProperty' or encountered a driver bug like #1523."; 052 053 054 public RowJdbc3KeyGenerator(String keyProperty) { 055 this.keyProperty = keyProperty; 056 } 057 058 @Override 059 public void processBefore(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { 060 // do nothing 061 } 062 063 @Override 064 public void processAfter(Executor executor, MappedStatement ms, Statement stmt, Object parameter) { 065 processBatch(ms, stmt, parameter); 066 } 067 068 public void processBatch(MappedStatement ms, Statement stmt, Object parameter) { 069 String[] keyProperties = new String[]{FlexConsts.ROW + "." + keyProperty}; 070 try (ResultSet rs = stmt.getGeneratedKeys()) { 071 final ResultSetMetaData rsmd = rs.getMetaData(); 072 final Configuration configuration = ms.getConfiguration(); 073 if (rsmd.getColumnCount() < keyProperties.length) { 074 // Error? 075 } else { 076 assignKeys(configuration, rs, rsmd, keyProperties, parameter); 077 } 078 } catch (Exception e) { 079 throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, e); 080 } 081 } 082 083 @SuppressWarnings("unchecked") 084 private void assignKeys(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, String[] keyProperties, 085 Object parameter) throws SQLException { 086 if (parameter instanceof ParamMap || parameter instanceof StrictMap) { 087 // Multi-param or single param with @Param 088 assignKeysToParamMap(configuration, rs, rsmd, keyProperties, (Map<String, ?>) parameter); 089 } else if (parameter instanceof ArrayList && !((ArrayList<?>) parameter).isEmpty() 090 && ((ArrayList<?>) parameter).get(0) instanceof ParamMap) { 091 // Multi-param or single param with @Param in batch operation 092 assignKeysToParamMapList(configuration, rs, rsmd, keyProperties, (ArrayList<ParamMap<?>>) parameter); 093 } else { 094 // Single param without @Param 095 assignKeysToParam(configuration, rs, rsmd, keyProperties, parameter); 096 } 097 } 098 099 private void assignKeysToParam(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, 100 String[] keyProperties, Object parameter) throws SQLException { 101 Collection<?> params = collectionize(parameter); 102 if (params.isEmpty()) { 103 return; 104 } 105 List<KeyAssigner> assignerList = new ArrayList<>(); 106 for (int i = 0; i < keyProperties.length; i++) { 107 assignerList.add(new KeyAssigner(configuration, rsmd, i + 1, null, keyProperties[i])); 108 } 109 Iterator<?> iterator = params.iterator(); 110 while (rs.next()) { 111 if (!iterator.hasNext()) { 112 throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, params.size())); 113 } 114 Object param = iterator.next(); 115 assignerList.forEach(x -> x.assign(rs, param)); 116 } 117 } 118 119 private void assignKeysToParamMapList(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, 120 String[] keyProperties, ArrayList<ParamMap<?>> paramMapList) throws SQLException { 121 Iterator<ParamMap<?>> iterator = paramMapList.iterator(); 122 List<KeyAssigner> assignerList = new ArrayList<>(); 123 long counter = 0; 124 while (rs.next()) { 125 if (!iterator.hasNext()) { 126 throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter)); 127 } 128 ParamMap<?> paramMap = iterator.next(); 129 if (assignerList.isEmpty()) { 130 for (int i = 0; i < keyProperties.length; i++) { 131 assignerList 132 .add(getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], keyProperties, false) 133 .getValue()); 134 } 135 } 136 assignerList.forEach(x -> x.assign(rs, paramMap)); 137 counter++; 138 } 139 } 140 141 private void assignKeysToParamMap(Configuration configuration, ResultSet rs, ResultSetMetaData rsmd, 142 String[] keyProperties, Map<String, ?> paramMap) throws SQLException { 143 if (paramMap.isEmpty()) { 144 return; 145 } 146 Map<String, Entry<Iterator<?>, List<KeyAssigner>>> assignerMap = new HashMap<>(); 147 for (int i = 0; i < keyProperties.length; i++) { 148 Entry<String, KeyAssigner> entry = getAssignerForParamMap(configuration, rsmd, i + 1, paramMap, keyProperties[i], 149 keyProperties, true); 150 Entry<Iterator<?>, List<KeyAssigner>> iteratorPair = MapUtil.computeIfAbsent(assignerMap, entry.getKey(), 151 k -> MapUtil.entry(collectionize(paramMap.get(k)).iterator(), new ArrayList<>())); 152 iteratorPair.getValue().add(entry.getValue()); 153 } 154 long counter = 0; 155 while (rs.next()) { 156 for (Entry<Iterator<?>, List<KeyAssigner>> pair : assignerMap.values()) { 157 if (!pair.getKey().hasNext()) { 158 throw new ExecutorException(String.format(MSG_TOO_MANY_KEYS, counter)); 159 } 160 Object param = pair.getKey().next(); 161 pair.getValue().forEach(x -> x.assign(rs, param)); 162 } 163 counter++; 164 } 165 } 166 167 private Entry<String, KeyAssigner> getAssignerForParamMap(Configuration config, ResultSetMetaData rsmd, 168 int columnPosition, Map<String, ?> paramMap, String keyProperty, String[] keyProperties, boolean omitParamName) { 169 Set<String> keySet = paramMap.keySet(); 170 // A caveat : if the only parameter has {@code @Param("param2")} on it, 171 // it must be referenced with param name e.g. 'param2.x'. 172 boolean singleParam = !keySet.contains(SECOND_GENERIC_PARAM_NAME); 173 int firstDot = keyProperty.indexOf('.'); 174 if (firstDot == -1) { 175 if (singleParam) { 176 return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName); 177 } 178 throw new ExecutorException("Could not determine which parameter to assign generated keys to. " 179 + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). " 180 + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are " 181 + keySet); 182 } 183 String paramName = keyProperty.substring(0, firstDot); 184 if (keySet.contains(paramName)) { 185 String argParamName = omitParamName ? null : paramName; 186 String argKeyProperty = keyProperty.substring(firstDot + 1); 187 return MapUtil.entry(paramName, new KeyAssigner(config, rsmd, columnPosition, argParamName, argKeyProperty)); 188 } else if (singleParam) { 189 return getAssignerForSingleParam(config, rsmd, columnPosition, paramMap, keyProperty, omitParamName); 190 } else { 191 throw new ExecutorException("Could not find parameter '" + paramName + "'. " 192 + "Note that when there are multiple parameters, 'keyProperty' must include the parameter name (e.g. 'param.id'). " 193 + "Specified key properties are " + ArrayUtil.toString(keyProperties) + " and available parameters are " 194 + keySet); 195 } 196 } 197 198 private Entry<String, KeyAssigner> getAssignerForSingleParam(Configuration config, ResultSetMetaData rsmd, 199 int columnPosition, Map<String, ?> paramMap, String keyProperty, boolean omitParamName) { 200 // Assume 'keyProperty' to be a property of the single param. 201 String singleParamName = nameOfSingleParam(paramMap); 202 String argParamName = omitParamName ? null : singleParamName; 203 return MapUtil.entry(singleParamName, new KeyAssigner(config, rsmd, columnPosition, argParamName, keyProperty)); 204 } 205 206 private static String nameOfSingleParam(Map<String, ?> paramMap) { 207 // There is virtually one parameter, so any key works. 208 return paramMap.keySet().iterator().next(); 209 } 210 211 private static Collection<?> collectionize(Object param) { 212 if (param instanceof Collection) { 213 return (Collection<?>) param; 214 } else if (param instanceof Object[]) { 215 return Arrays.asList((Object[]) param); 216 } else { 217 return Arrays.asList(param); 218 } 219 } 220 221 private class KeyAssigner { 222 223 private final Configuration configuration; 224 private final ResultSetMetaData rsmd; 225 private final TypeHandlerRegistry typeHandlerRegistry; 226 private final int columnPosition; 227 private final String paramName; 228 private final String propertyName; 229 private TypeHandler<?> typeHandler; 230 231 protected KeyAssigner(Configuration configuration, ResultSetMetaData rsmd, int columnPosition, String paramName, 232 String propertyName) { 233 super(); 234 this.configuration = configuration; 235 this.rsmd = rsmd; 236 this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); 237 this.columnPosition = columnPosition; 238 this.paramName = paramName; 239 this.propertyName = propertyName; 240 } 241 242 protected void assign(ResultSet rs, Object param) { 243 if (paramName != null) { 244 // If paramName is set, param is ParamMap 245 param = ((ParamMap<?>) param).get(paramName); 246 } 247 MetaObject metaParam = configuration.newMetaObject(param); 248 try { 249 if (typeHandler == null) { 250 if (metaParam.hasSetter(propertyName)) { 251 Class<?> propertyType = metaParam.getSetterType(propertyName); 252 typeHandler = typeHandlerRegistry.getTypeHandler(propertyType, 253 JdbcType.forCode(rsmd.getColumnType(columnPosition))); 254 } else { 255 throw new ExecutorException("No setter found for the keyProperty '" + propertyName + "' in '" 256 + metaParam.getOriginalObject().getClass().getName() + "'."); 257 } 258 } 259 if (typeHandler == null) { 260 // Error? 261 } else { 262 Object value = typeHandler.getResult(rs, columnPosition); 263 metaParam.setValue(propertyName, value); 264 } 265 } catch (SQLException e) { 266 throw new ExecutorException("Error getting generated key or setting result to parameter object. Cause: " + e, 267 e); 268 } 269 } 270 271 } 272 273}