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