MyBatis --自定义实现MyBatis 查询过程 (简略化处理)
说明:
项目地址: MyBatis-source-learn
sql 地址:test.sql
项目说明: 一个简单的解析注解的select查询过程示例.
PS: 若文章字体偏大或者偏小,建议通过 ctrl键+鼠标滑轮 进行修改,以提升阅读效果.(带来不便,请谅解!)
MyBatis 实现查询单条SQL语句的整体流程:
- 建立连接, 获取Connection
- 解析xml文件 | 解析注解的方式 获得 SQL语句 ,方法的参数列表和返回值类型
- 执行SQL命令,返回结果
- 查询缓存, 有的话直接返回,没有继续执行
- JDBC 执行查询命令
- 处理结果集与方法返回值的映射.
简易查询思路:
- 建立与数据库的连接, 获取Connection
- 采用代理对象(JDK动态代理的方式)执行查询操作
- xml方式有点繁琐, out . 通过解析方法注解Annotation的方式获取SQL语句,方法参数等,
- 执行方法
- 与数据库交互(JDBC),获取结果集
- 结果集与方法返回类型绑定(未实现)
具体实现:
UserMapper.java
interface UserMapper {
@Select("select * from user where id =#{id}")
// List<User> selectUser(int id);
User selectUserById(int id);
}
生成代理对象:
/**
* 手动实现MyBatis select 查询方法 注解方式
* 1. 通过反射获取 usermapper
* 1. 获取SQL 命令, param 参数列表, 结果集类型
* 1-1. 解析注解
* 1-2 . 处理方法请求参数
* 1-3. 封装SQL语句
* 1-4. 获取方法的返回值, 并且处理 (未做)
* 2. 与数据库进行交互, 获取结果集
* 3. 处理结果集, 与返回值绑定, ()未做
* 4. 返回给用户
* @return
*/
UserMapper getUsermapper() {
UserMapper mapper = (UserMapper) Proxy.newProxyInstance(MyMyBatis.class.getClassLoader(),
new Class<?>[]{UserMapper.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Select annotation = method.getAnnotation(Select.class);
if (annotation != null) {
String[] value = annotation.value();
String sql = value[0];
// 1, 处理方法参数
// method.invoke()
Map map = parseMethodArgs(args, method);
//2. 处理SQL语句 ,将参数 与SQL 语句绑定 即通过这个方法获取到一条完整的sql命令
sql = parseSQL(sql, map);
System.out.println(sql);
// 3. 获取方法的返回类型
Class<?> returnType = method.getReturnType();
//4 执行SQL方法, 处理结果集
return runSQL(sql, returnType);
}
return null;
}
});
return mapper;
}
处理方法参数:
/**
* 解析方法参数, 将方法参数通过map的形式返回
* @param args 方法 参数
* @param method 具体方法
* @return map
*/
private Map<String, Object> parseMethodArgs(Object[] args, Method method) {
Map<String, Object> map = new HashMap<String, Object>();
Parameter[] parameters = method.getParameters();
int[] cnt = {0};
Arrays.asList(parameters).forEach(parameter -> {
String name = parameter.getName();
map.put(name, args[cnt[0]++]);
});
return map;
}
生成SQL语句:
/**
* 解析SQL语句
* // TODO: 2021/3/15 存在问题, 应该把 #{} 更改为 ? 并且保存参数名列表,
*
* @param sql 初始sql语句 带#{}
* @param map 参数map
* @return sql
*/
private String parseSQL(String sql, Map map) {
StringBuilder sqlB = new StringBuilder();
char[] chars = sql.toCharArray();
for (int i = 0; i < chars.length; i++) {
char c = chars[i];
if (c == '#') {
StringBuilder partB = new StringBuilder();
i = parsePartSql(sql, partB, i);
Object o = map.get(partB.toString());
sqlB.append(o.toString());
} else {
sqlB.append(c);
}
}
return sqlB.toString();
}
private int parsePartSql(String sql, StringBuilder partB, Integer i) {
i++;
for (; i < sql.length(); i++) {
if (sql.charAt(i) != '{') {
throw new RuntimeException("sql error without #{}in");
} else {
while (true) {
i++;
if (i == sql.length() || sql.charAt(i) == '}') {
return ++i;
} else {
partB.append(sql.charAt(i));
}
}
}
}
return ++i;
}
执行SQL命令:
/**
* 执行SQL 命令返回结果集
* // TODO: 2021/3/15 未处理 方法的返回值
*
* @param sql sql 命令
* @param returnType 结果的返回类型
* @return 结果集
*/
private Object runSQL(String sql, Class<?> returnType) {
DataSource dataSource = getDataSource();
Object result = null;
try (Connection connection = dataSource.getConnection()) {
PreparedStatement preparedStatement = connection.prepareStatement(sql);
// preparedStatement.set
ResultSet resultSet = preparedStatement.executeQuery();
// 处理结果集, 对结果集 与xml方法 返回值做映射,
result = resolveResultSet(resultSet, returnType);
// preparedStatement
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
获取数据库连接:
private static DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("aa12321.");
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true");
return dataSource;
}
处理结果集ResultSet:
/**
* // TODO: 2021/3/15 暂时不知道如何处理结果集 , 先通过打印的方式进行处理
* 处理结果集, 并将结果集与 method 返回值绑定
* @param resultSet
* @param returnType
* @return
*/
private Object resolveResultSet(ResultSet resultSet, Class<?> returnType) throws SQLException, IllegalAccessException, InstantiationException {
// returnType.getMethods();
// 1. 获取 方法的返回值 对象实例化, // TODO: 2021/3/15 暂不处理
User user = new User();
//2. 获取结果集列表,并将参数绑定
while (resultSet.next()) {
int id = resultSet.getInt("id");
String name = resultSet.getString("name");
String username = resultSet.getString("username");
String password = resultSet.getString("password");
System.out.println(id + "," + name);
user.setId(id);
user.setName(name);
user.setUsername(username);
user.setPassowrd(password);
}// 3. 关闭结果集
if (!resultSet.isClosed()) {
resultSet.close();
}
return user;
}
测试方法:
public static void main(String[] args) {
MyMyBatis sol = new MyMyBatis();
UserMapper usermapper = sol.getUsermapper();
User user = usermapper.selectUserById(1);
System.out.println(user);
}
执行结果:
总结:
本测试程序只是简单的模拟了一下 MyBatis 查询过程, 没有根据method.getReturnType() 来映射ResultSet.
TODO:
- 自己需要去了解下java.lang.reflect 这个包的内容 ,这样自己就会更好的实现instance
猜你喜欢