简易mybatis实现

发布时间 2023-12-27 15:21:30作者: KeepSmiling_me
package com.ibatis.io.entity;


public class User {
  private Integer id;
  private String name;

  private Integer age;

  public Integer getId() {
      return id;
  }

  public void setId(Integer id) {
      this.id = id;
  }

  public String getName() {
      return name;
  }

  public void setName(String name) {
      this.name = name;
  }

  public Integer getAge() {
      return age;
  }

  public void setAge(Integer age) {
      this.age = age;
  }

  @Override
  public String toString() {
      return "User{" +
              "id=" + id +
              ", name='" + name + '\'' +
              ", age=" + age +
              '}';
  }
}
package com.ibatis.io.mapper;

import com.ibatis.io.entity.User;
import com.ibatis.io.mybatis.Param;
import com.ibatis.io.mybatis.Select;

import java.util.List;


public interface UserMapper {

  @Select("select * from user where name=#{name}")
  public List<User> getUser(@Param("name") String name,@Param("age") Integer age);

  @Select("select * from user where id=#{id}")
  public User getUserById(@Param("id") Integer id);
}


package com.ibatis.io.mybatis;

public class GenericTokenParser {

  private final String openToken;
  private final String closeToken;
  private final TokenHandler handler;

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
      this.openToken = openToken;
      this.closeToken = closeToken;
      this.handler = handler;
  }

  public String parse(String text) {
      if (text == null || text.isEmpty()) {
          return "";
      }
      // search open token
      int start = text.indexOf(openToken, 0);
      if (start == -1) {
          return text;
      }
      char[] src = text.toCharArray();
      int offset = 0;
      final StringBuilder builder = new StringBuilder();
      StringBuilder expression = null;
      while (start > -1) {
          if (start > 0 && src[start - 1] == '\\') {
              // this open token is escaped. remove the backslash and continue.
              builder.append(src, offset, start - offset - 1).append(openToken);
              offset = start + openToken.length();
          } else {
              // found open token. let's search close token.
              if (expression == null) {
                  expression = new StringBuilder();
              } else {
                  expression.setLength(0);
              }
              builder.append(src, offset, start - offset);
              offset = start + openToken.length();
              int end = text.indexOf(closeToken, offset);
              while (end > -1) {
                  if (end > offset && src[end - 1] == '\\') {
                      // this close token is escaped. remove the backslash and continue.
                      expression.append(src, offset, end - offset - 1).append(closeToken);
                      offset = end + closeToken.length();
                      end = text.indexOf(closeToken, offset);
                  } else {
                      expression.append(src, offset, end - offset);
                      offset = end + closeToken.length();
                      break;
                  }
              }
              if (end == -1) {
                  // close token was not found.
                  builder.append(src, start, src.length - start);
                  offset = src.length;
              } else {
                  builder.append(handler.handleToken(expression.toString()));
                  offset = end + closeToken.length();
              }
          }
          start = text.indexOf(openToken, offset);
      }
      if (offset < src.length) {
          builder.append(src, offset, src.length - offset);
      }
      return builder.toString();
  }
}
package com.ibatis.io.mybatis;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;


public class IntegerTypeHandler implements TypeHandler<Integer> {
  @Override
  public void setParameter(PreparedStatement statement, int i, Integer value) throws SQLException {
      statement.setInt(i, value);
  }

  @Override
  public Integer getResult(ResultSet rs, String columnName) throws SQLException {
      return rs.getInt(columnName);
  }
}
package com.ibatis.io.mybatis;


import java.lang.reflect.*;
import java.sql.*;
import java.util.*;


public class MapperProxyFactory {

  private static final Map<Class<?>, TypeHandler> typeHandlerMap = new HashMap<>();



  static {
      try {
          typeHandlerMap.put(String.class, new StringTypeHandler());
          typeHandlerMap.put(Integer.class, new IntegerTypeHandler());
          Class.forName("com.mysql.cj.jdbc.Driver");
      } catch (ClassNotFoundException e) {
          throw new RuntimeException(e);
      }
  }

  public static <T> T getMapper(Class<T> mapper) {
      Object proxyInstance = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[]{mapper}, new InvocationHandler() {
          @Override
          public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
              // 获取数据库连接
              Connection conn = getConnection();
              Select annotation = method.getAnnotation(Select.class);
              String sql = annotation.value();

              Parameter[] parameters = method.getParameters();
              HashMap<String, Object> map = new HashMap<>();
              for (int i = 0; i < parameters.length; i++) {
                  String parameter = parameters[i].getAnnotation(Param.class).value();
                  map.put(parameter, args[i]);
              }

              // 解析 SQL,并将 #{} 替换为 ?
              ParameterMappingTokenHandler tokenHandler = new ParameterMappingTokenHandler();
              GenericTokenParser parser = new GenericTokenParser("#{", "}", tokenHandler);
              String parse = parser.parse(sql);
              // 结果支持List或单个User对象
              Object result = null;
              // 执行 SQL
              List<Object> users;

              ResultSetMetaData metaData;
              try (PreparedStatement preparedStatement = conn.prepareStatement(parse)) {
                  List<ParameterMapping> parameterMappings = tokenHandler.getParameterMappings();
                  for (int i = 0; i < parameterMappings.size(); i++) {
                      String property = parameterMappings.get(i).getProperty();
                      Class<?> aClass = map.get(property).getClass();
                      typeHandlerMap.get(aClass).setParameter(preparedStatement, i + 1, map.get(property));

                  }
                  preparedStatement.execute();
                  Class resultType = null;
                  Type genericReturnType = method.getGenericReturnType();
                  if (genericReturnType instanceof Class) {
                      resultType = (Class) genericReturnType;
                  } else if (genericReturnType instanceof ParameterizedType) {
                      Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
                      resultType = (Class) actualTypeArguments[0];
                  }
                  users = new ArrayList<>();
                  metaData = preparedStatement.getMetaData();
                  List<String> columnList = new ArrayList<>();
                  for (int i = 0; i < metaData.getColumnCount(); i++) {
                      columnList.add(metaData.getColumnName(i + 1));
                  }

                  HashMap<String, Method> setMap = new HashMap<>();
                  for (Method declaredMethod : resultType.getDeclaredMethods()) {
                      if (declaredMethod.getName().startsWith("set")) {
                          String substring = declaredMethod.getName().substring(3);
                          substring = substring.substring(0, 1).toLowerCase(Locale.ROOT) + substring.substring(1);
                          setMap.put(substring, declaredMethod);
                      }
                  }

                  ResultSet resultSet = preparedStatement.getResultSet();
                  while (resultSet.next()) {
                      Object instance = resultType.getDeclaredConstructor().newInstance();
                      for (int i = 0; i < columnList.size(); i++) {
                          String column = columnList.get(i);
                          Method setMethod = setMap.get(column);
                          Class<?> parameterType = setMethod.getParameterTypes()[0];
                          TypeHandler typeHandler = typeHandlerMap.get(parameterType);
                          setMethod.invoke(instance, typeHandler.getResult(resultSet, column));
                      }
                      users.add(instance);
                  }


                  if (method.getReturnType().equals(List.class)) {
                      result = users;
                  } else {
                      result = users.get(0);
                  }
              }


              return result;
          }
      });
      return (T) proxyInstance;
  }

  private static Connection getConnection() throws SQLException {
      return DriverManager.getConnection("jdbc:mysql://127.0.0.1:3306/test?serverTimezone=UTC", "root", "123456");
  }


}
package com.ibatis.io.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Param {

  String value();
}

package com.ibatis.io.mybatis;

public class ParameterMapping {
  private String property;

  public ParameterMapping(String property) {
      this.property = property;
  }

  public String getProperty() {
      return property;
  }

  public void setProperty(String property) {
      this.property = property;
  }
}
package com.ibatis.io.mybatis;

import java.util.ArrayList;
import java.util.List;

public class ParameterMappingTokenHandler implements TokenHandler {
  private final List<ParameterMapping> parameterMappings = new ArrayList();


  public List<ParameterMapping> getParameterMappings() {
      return this.parameterMappings;
  }

  public String handleToken(String content) {
      this.parameterMappings.add(new ParameterMapping(content));
      return "?";
  }


}
package com.ibatis.io.mybatis;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* @Created by ggz
* @Date 2023/12/26 14:01
* @Version 1.0.0
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {

  String value();
}
package com.ibatis.io.mybatis;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
* @Created by ggz
* @Date 2023/12/26 17:17
* @Version 1.0.0
*/
public class StringTypeHandler implements TypeHandler<String>{
  @Override
  public void setParameter(PreparedStatement statement, int i, String value) throws SQLException {
      statement.setString(i, value);
  }

  @Override
  public String getResult(ResultSet rs, String columnName) throws SQLException {
      return rs.getString(columnName);
  }
}
package com.ibatis.io.mybatis;

public interface TokenHandler {
  String handleToken(String content);
}
package com.ibatis.io.mybatis;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
* @Created by ggz
* @Date 2023/12/26 17:15
* @Version 1.0.0
*/
public interface TypeHandler<T> {
  void setParameter(PreparedStatement statement, int i, T value) throws SQLException;

  T getResult(ResultSet rs, String columnName) throws SQLException;
}
package com.ibatis.io;

import com.ibatis.io.entity.User;
import com.ibatis.io.mapper.UserMapper;
import com.ibatis.io.mybatis.MapperProxyFactory;

import java.util.List;

/**
* @Created by ggz
* @Date 2023/12/26 14:24
* @Version 1.0.0
*/
public class App {
  public static void main(String[] args) {
      UserMapper mapper = MapperProxyFactory.getMapper(UserMapper.class);
      List<User> users = mapper.getUser("小米",20);
      System.out.println(users);
      User userById = mapper.getUserById(2);
      System.out.println(userById);
  }
}
/*
Navicat Premium Data Transfer

Source Server         : local
Source Server Type   : MySQL
Source Server Version : 80027
Source Host           : localhost:3306
Source Schema         : test

Target Server Type   : MySQL
Target Server Version : 80027
File Encoding         : 65001

Date: 27/12/2023 15:14:42
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` bigint(0) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL,
`age` int(0) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, '小米', 20);
INSERT INTO `user` VALUES (2, '大米', 22);

SET FOREIGN_KEY_CHECKS = 1;