Handwritten simplified version of MyBatis
Pay attention to the public account “Learn CRUD every day”, more basic knowledge, interview questions to learn and share.
Basic execution step
The implementation principle of Mybatis is sorted out, and the understanding of Mybatis is deepened through handwritten simple version. MyBatis source code interpretation
Mybatis method execution flow
- 1. Configure resolution
- 2. Obtain the session
- 3. Obtain the Mapper proxy object
- 4. Execution method
Configure the parsing
The Configuration class
public class Configuration {
/** * Data source configuration */
DataSource dataSource;
/** * Configuration information */
public final Map<String, Object> configMap = new HashMap<>();
/** * Mapper */
public finalMap<Class<? >, MapperProxyFactory<? >> mapperMap =new ConcurrentHashMap<>();
private Configuration(a) {}public DataSource getDataSource(a) {
return dataSource;
}
public Configuration(String configFileName) throws IOException {
Properties properties = new Properties();
// Config file load
try (InputStream in = Configuration.class.getClassLoader().getResourceAsStream(configFileName)) {
properties.load(in);
}
// Configure-type configuration
for (ConfigKeyEnums configKey : ConfigKeyEnums.values()) {
configMap.put(configKey.getKey(), properties.get(configKey.getKey()));
}
/ / register mapper
final String packages = configMap.get(ConfigKeyEnums.MAPPER_PACKAGES.getKey()).toString();
if (packages == null || packages.length() == 0) {
throw new IOException("packages can't not be bank");
}
final String[] packageArr = packages.split(",");
for (String pkg : packageArr) {
if(pkg ! =null && pkg.length() > 0) { scanMapper(pkg); }}// Data source creation
this.dataSource = new DefaultDataSource(configMap.get(ConfigKeyEnums.JDBC_DRIVER.getKey()).toString(),
configMap.get(ConfigKeyEnums.JDBC_URL.getKey()).toString(),
configMap.get(ConfigKeyEnums.JDBC_USERNAME.getKey()).toString(),
configMap.get(ConfigKeyEnums.JDBC_PWD.getKey()).toString());
}
/ * * *@param packageName
*/
private void scanMapper(String packageName) {
// The packet to scan
Reflections reflections = new Reflections(ClasspathHelper.forPackage(packageName, this.getClass().getClassLoader()),
new TypeAnnotationsScanner(),
new SubTypesScanner(false)); Set<Class<? >> classesList = reflections.getTypesAnnotatedWith(Mapper.class);ExecutorBean // store the url and ExecutorBean mapping
for(Class<? > clazz : classesList) { MapperProxyFactory<? > mapperProxyFactory =newMapperProxyFactory<>(clazz); mapperMap.put(clazz, mapperProxyFactory); }}}Copy the code
Mapper.package is configured to make the scan package, and annotations are added to the interface class to identify the Mapper used. Create a new proxy class to encapsulate mapper
MapperProxyFactory
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public T newInstance(SqlSession session) {
MapperProxy<T> mapperProxy = new MapperProxy<T>(session, mapperInterface, methodCache);
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), newClass[]{mapperInterface}, mapperProxy); }}Copy the code
MapperProxy
Mapper dynamic proxy class
public class MapperProxy<T> implements InvocationHandler {
private static final Set<Class<? extends Annotation>> STATEMENT_ANNOTATION_TYPES = Stream
.of(Select.class, Update.class, Insert.class, Delete.class)
.collect(Collectors.toSet());
private final SqlSession sqlSession;
private final Map<Method, MapperMethodInvoker> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
this.sqlSession = sqlSession;
this.methodCache = methodCache;
}
/** * method executes *@param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}
// Annotation fetch
Annotation sqlAnnotation = STATEMENT_ANNOTATION_TYPES.stream()
.flatMap(x -> Arrays.stream(method.getAnnotationsByType(x))).findFirst().orElse(null);
final Optional<SqlCommandType> sqlCommandType = getSqlCommandType(sqlAnnotation);
if (sqlCommandType.isPresent()) {
return methodCache.computeIfAbsent(method, m -> {
final MapperMethod mapperMethod = new MapperMethod(sqlCommandType.get(),
new ParameterHandler(method),
new ResultHandler(method.getGenericReturnType()));
mapperMethod.setSql(method, sqlAnnotation);
return new DefaultMethodInvoker(mapperMethod);
}).invoke(proxy, method, args, sqlSession);
}
throw new Throwable("err mapper method");
}
private Optional<SqlCommandType> getSqlCommandType(Annotation annotation) {
SqlCommandType commandType = null;
if (annotation instanceof Select) {
commandType = SqlCommandType.SELECT;
} else if (annotation instanceof Update) {
commandType = SqlCommandType.UPDATE;
} else if (annotation instanceof Insert) {
commandType = SqlCommandType.INSERT;
} else if (annotation instanceof Delete) {
commandType = SqlCommandType.DELETE;
}
returnOptional.ofNullable(commandType); }}Copy the code
Mapper method input parameter mapping parsing
ParameterHandler handles the mapping between mapper method parameters and parameter placeholders in SQL
public class ParameterHandler {
private SortedMap<String, Integer> paramNameMap = new TreeMap<>();
public SortedMap<String, Integer> getParamMap(a) {
return paramNameMap;
}
private ParameterHandler(a) {}public ParameterHandler(Method method) {
initParam(method);
}
private void initParam(Method method) {
finalClass<? >[] paramTypes = method.getParameterTypes();final Annotation[][] paramAnnotations = method.getParameterAnnotations();
final SortedMap<String, Integer> map = new TreeMap<>();
int paramCount = paramTypes.length;
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
String name = null;
for (Annotation annotation : paramAnnotations[paramIndex]) {
if (annotation instanceof Param) {
name = ((Param) annotation).value();
break; }}if (name == null) {
name = "" + map.size();
}
map.put(name, paramIndex);
}
paramNameMap = Collections.unmodifiableSortedMap(map);
}
public void setParam(PreparedStatement ps, Object[] args) throws SQLException {
for (int i = 0, size = args.length; i < size; i++) {
Object arg = args[i];
if (arg instanceof Integer) {
ps.setInt(i + 1, (Integer) arg);
} else if (arg instanceof Long) {
ps.setLong(i + 1, (Long) arg);
} else if (arg instanceof Boolean) {
ps.setBoolean(i + 1, (Boolean) arg);
} else {
ps.setString(i + 1, (String) arg);
}
// TODO supports type extension}}}Copy the code
The mapper method returns object map resolution
ResultHandler handles the setting of the output parameter of the execution result
@Slf4j
public class ResultHandler {
private Type returnType;
private ResultHandler(a) {}public ResultHandler(Type returnType) {
this.returnType = returnType;
}
public Type getReturnType(a){
return returnType;
}
public List<Object> result(ResultSet resultSet) throws SQLException {
Type objectType = returnType;
if (returnType instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) returnType;
for(Type arg : pt.getActualTypeArguments()) { objectType = arg; }}finalClass<? > returnClass = (Class<? >)objectType; List<Object> resultList =new ArrayList<>();
while (resultSet.next()) {
try {
Object result = returnClass.newInstance();
final Field[] fields = returnClass.getDeclaredFields();
for (Field field : fields) {
finalClass<? > type = field.getType();final String fieldName = Strings.upperFirst(field.getName());
final Method method = returnClass.getMethod("set" + fieldName, type);
if (type == Integer.class) {
method.invoke(result, resultSet.getInt(fieldName));
}
if (type == Long.class) {
method.invoke(result, resultSet.getLong(fieldName));
}
if (type == String.class) {
method.invoke(result, resultSet.getString(fieldName));
}
if (type == Boolean.class) {
method.invoke(result, resultSet.getBoolean(fieldName));
}
}
resultList.add(result);
} catch (Exception e) {
log.error("result error", e);
throw new RuntimeException("result set err", e); }}returnresultList; }}Copy the code
OpenSession Obtains the session
Create a default session object
@Slf4j
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private DefaultSqlSession(a) {}public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <T> T getMapper(Class<T> mapperInterface) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) configuration.mapperMap.get(mapperInterface);
return mapperProxyFactory.newInstance(this);
}
@Override
public <T> T selectOne(MapperMethod mapperMethod, List<Object> param) throws SQLException {
final List<Object> objects = selectList(mapperMethod, param);
if (objects.size() > 1) {
throw new SQLException("result more than one");
}
return objects.size() == 0 ? null : (T) objects.get(0);
}
private void prepareStatement(PreparedStatement ps, List<Object> args) throws SQLException {
for (int i = 0, size = args.size(); i < size; i++) {
Object arg = args.get(i);
if (arg instanceof Integer) {
ps.setInt(i + 1, (Integer) arg);
} else if (arg instanceof Long) {
ps.setLong(i + 1, (Long) arg);
} else if (arg instanceof Boolean) {
ps.setBoolean(i + 1, (Boolean) arg);
} else {
ps.setString(i + 1, (String) arg);
}
// TODO supports type extension}}@Override
public <E> List<E> selectList(MapperMethod mapperMethod, List<Object> param) throws SQLException {
try (final PreparedStatement preparedStatement = getPrepareStatement(mapperMethod, param)) {
try (final ResultSet resultSet = preparedStatement.executeQuery()) {
return(List<E>) mapperMethod.getResultHandler().result(resultSet); }}}@Override
public int update(MapperMethod mapperMethod, List<Object> param) throws SQLException {
try (final PreparedStatement preparedStatement = getPrepareStatement(mapperMethod, param)) {
returnpreparedStatement.executeUpdate(); }}private PreparedStatement getPrepareStatement(MapperMethod mapperMethod, List<Object> param) throws SQLException {
final String sql = mapperMethod.getSql();
final Connection connection = configuration.getDataSource().getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
prepareStatement(preparedStatement, param);
if ("true".equals(configuration.configMap.getOrDefault(ConfigKeyEnums.SQL_LOG.getKey(), "false"))) {
log.info("execute sql:{}", preparedStatement.toString());
}
return preparedStatement;
}
@Override
public int delete(MapperMethod mapperMethod, List<Object> param) throws SQLException {
return update(mapperMethod, param);
}
@Override
public int insert(MapperMethod mapperMethod, List<Object> param) throws SQLException {
returnupdate(mapperMethod, param); }}Copy the code
GetMapper get Mapper
Obtain the Mapper proxy object scanned by Configuration
@Override
public <T> T getMapper(Class<T> mapperInterface) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) configuration.mapperMap.get(mapperInterface);
return mapperProxyFactory.newInstance(this);
}
Copy the code
Execute the mapper method
MapperProxy#invoke->MapperMethod#execute-> sqlssession #
JDBC implementation
DefaultSqlSession, handles JDBC execution
@Slf4j
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private DefaultSqlSession(a) {}public DefaultSqlSession(Configuration configuration) {
this.configuration = configuration;
}
@Override
public <T> T getMapper(Class<T> mapperInterface) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) configuration.mapperMap.get(mapperInterface);
return mapperProxyFactory.newInstance(this);
}
@Override
public <T> T selectOne(MapperMethod mapperMethod, List<Object> param) throws SQLException {
final List<Object> objects = selectList(mapperMethod, param);
if (objects.size() > 1) {
throw new SQLException("result more than one");
}
return objects.size() == 0 ? null : (T) objects.get(0);
}
private void prepareStatement(PreparedStatement ps, List<Object> args) throws SQLException {
for (int i = 0, size = args.size(); i < size; i++) {
Object arg = args.get(i);
if (arg instanceof Integer) {
ps.setInt(i + 1, (Integer) arg);
} else if (arg instanceof Long) {
ps.setLong(i + 1, (Long) arg);
} else if (arg instanceof Boolean) {
ps.setBoolean(i + 1, (Boolean) arg);
} else {
ps.setString(i + 1, (String) arg);
}
// TODO supports type extension}}@Override
public <E> List<E> selectList(MapperMethod mapperMethod, List<Object> param) throws SQLException {
try (final PreparedStatement preparedStatement = getPrepareStatement(mapperMethod, param)) {
try (final ResultSet resultSet = preparedStatement.executeQuery()) {
return(List<E>) mapperMethod.getResultHandler().result(resultSet); }}}@Override
public int update(MapperMethod mapperMethod, List<Object> param) throws SQLException {
try (final PreparedStatement preparedStatement = getPrepareStatement(mapperMethod, param)) {
returnpreparedStatement.executeUpdate(); }}private PreparedStatement getPrepareStatement(MapperMethod mapperMethod, List<Object> param) throws SQLException {
final String sql = mapperMethod.getSql();
final Connection connection = configuration.getDataSource().getConnection();
final PreparedStatement preparedStatement = connection.prepareStatement(sql);
prepareStatement(preparedStatement, param);
if ("true".equals(configuration.configMap.getOrDefault(ConfigKeyEnums.SQL_LOG.getKey(), "false"))) {
log.info("execute sql:{}", preparedStatement.toString());
}
return preparedStatement;
}
@Override
public int delete(MapperMethod mapperMethod, List<Object> param) throws SQLException {
return update(mapperMethod, param);
}
@Override
public int insert(MapperMethod mapperMethod, List<Object> param) throws SQLException {
returnupdate(mapperMethod, param); }}Copy the code
Attached git project address mainstream framework technology deconstruction