承接去年记录的mybatis,从架构和源码的角度剖析mybatis。从整个项目工程来看他比spring小得多,因此代码更易读。使用他时,通常我们要做的就是编写sql和接口,这篇首先从sql解析开始。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
| public class Test {
static class User { int age; int name; }
public static void main(String[] args) { UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(Test.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); Map<String, Object> map = buildMethodArgNameMap(method, args);
System.out.println(map.toString()); if (annotation != null) { String[] value = annotation.value(); String s = parseSql(value[0], map); System.out.println(s); } return null; } }); userMapper.selectUesList(2); }
public static String parseSql(String sql, Map<String, Object> map) { StringBuilder sb = new StringBuilder(); int length = sql.length(); for (int i = 0; i < length; i++) { char c = sql.charAt(i); if (c == '#') { int nextIndex = i + 1; char nextChar = sql.charAt(nextIndex); if (nextChar != '{') { throw new RuntimeException("sql写法错误,应该是 { "); } StringBuilder argSb = new StringBuilder(); i = parseSqlArg(argSb, sql, nextIndex); String argName = argSb.toString(); Object argValue = map.get(argName); sb.append(argValue.toString()); continue; } sb.append(c); }
return sb.toString(); }
private static int parseSqlArg(StringBuilder argSb, String sql, int nextIndex) { for (int i = nextIndex + 1; i < sql.length(); i++) { char c = sql.charAt(i); if (c != '}') { argSb.append(c); } else { return i; } } throw new RuntimeException("sql写法错误,应该是 } "); }
public static Map<String, Object> buildMethodArgNameMap(Method method, Object[] args) { Map<String, Object> map = new HashMap<>(6); Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { map.put(parameters[i].getName(), args[i]); } return map; } }
interface UserMapper {
@Select("select * from User where id = #{id}") List<Test.User> selectUesList(Integer id); }
|
以上只是模拟mybatis架构中的解析sql的部分,真正的解析还是比较复杂的,要考虑很多种情况,但是原理是一样,都离不开动态代理。下面我们看看他的架构设计图(非官方):
我们从这个角度去想:如何用sql去跟数据库交互?如何动态地去用sql与数据库交互?站在现在看过去,mybatis实现了这一点,如何做的?利用接口来实现,有两种方式:在接口上用注释来写或者写在xml文件里需要替换的地方用#{}
括起来,在需要的地方调用接口的方法并把值传进去,我们要做的就只有这些,其他的mybatis帮我们做了。我们来看看他都做了什么,首先就是映射关系,把传入的值替换到sql语句并拼接起来、查询的结果映射到想要的对象上;管理sql与数据库的交互。这两个是最基本的,其次,还可以做些优化,比如缓存,频繁地查询某个sql可以将结果存起来;扩展性,对结果统一处理等。