内容简介:创建一个接口UserMapper,再创建一个实体类User使用JDK的动态代理,创建一个代理处理器包装上面的代理
- 如何在没有实例的情况下创建Mapping接口的实现类并且调用接口中的方法
- 使用字节技术创建子类
- 使用匿名内部类
- 使用动态代理创建对象(我们使用这个)
创建一个接口UserMapper,再创建一个实体类User
使用JDK的动态代理,创建一个代理处理器
public class InvocationHandlerMybatis implements InvocationHandler { /** * * @param proxy 代理对象 * @param method 拦截的方法 * @param args 方法上的参数 * @return * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始代理"); return 1; } } 复制代码
包装上面的代理
public class SqlSession { /**加载Mapper接口*/ public static <T> T getMapper(Class clazz) { return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new InvocationHandlerMybatis()); } } 复制代码
使用测试类测试一下
public static void main(String[] args) { UserMapper userMapper = SqlSession.getMapper(UserMapper.class); int i = userMapper.insertUser("", ""); System.out.println(i); } 复制代码
测试结果如下
这样就可以实现拿到接口的方法参数并且自行控制对象的返回值
一、 @Insert
的实现步骤
@Insert
具体实现如下
/** * @author libi * 用于动态代理,获取方法的参数并且给返回值 */ public class InvocationHandlerMybatis implements InvocationHandler { /** * @param proxy 代理对象 * @param method 拦截的方法 * @param args 方法上的参数 * @return 方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始代理"); //判断方法上是否存在Insert注解 ExtInsert extInsert = method.getDeclaredAnnotation(ExtInsert.class); if (extInsert != null) { //执行插入的操作,返回影响行数 return doInsert(method, args, extInsert); } return null; } /** * 执行插入的操作 * @param method * @param args * @param extInsert * @return */ private int doInsert(Method method, Object[] args, ExtInsert extInsert) { //获取 Sql 语句 String sql = extInsert.value(); System.out.println("insert sql:" + sql); //获取方法参数和Sql语句进行匹配 //定义一个Map,Key是参数名,Value是参数值 ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>(); //获取方法上的参数 Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //获取参数名称和参数的值 ExtParam param = parameters[i].getDeclaredAnnotation(ExtParam.class); if (param != null) { String name = param.value(); Object value = args[i]; System.out.println("paramName:"+name+",paramValue:"+value); map.put(name, value); } } //怕打乱顺序而把sql语句的参数放在一个有序的数组里 List<Object> sqlParam = new ArrayList<>(); String[] sqlInsertParameter = SQLUtils.sqlInsertParameter(sql); for (String paramName : sqlInsertParameter) { Object paramValue = map.get(paramName); sqlParam.add(paramValue); } System.out.println(); //把参数替换成? sql = SQLUtils.parameQuestion(sql, sqlInsertParameter); System.out.println("new sql:"+sql); //执行JDBC return JDBCUtils.insert(sql, false, sqlParam); } } 复制代码
测试这个方法
我们定义一个Mapper
public interface UserMapper { @ExtInsert("insert into user(username,password) values (#{userName},#{password})") int insertUser(@ExtParam("userName") String userName, @ExtParam("password") String password); } 复制代码
然后再主函数里使用代理调用这个方法
public class Cluster { public static void main(String[] args) { UserMapper userMapper = SqlSession.getMapper(UserMapper.class); int i = userMapper.insertUser("name", "123"); System.out.println(i); } } 复制代码
然后运行结果如下
二、 @Select
的实现思路
- 找到方法里带有
@Select
注解的方法,拿到Spl语句 - 获取方法上的参数,绑定,然后把参数替换成?
- 调用JDBC调用底层
- 使用反射机制实例化实体类对象(获取方法返回的类型,使用反射实例化对象)
核心代码如下
和上面不同的是,我重构了InvocationHandleMybatis类的代码,复用了一些代码
/** * 执行查询的操作 * @param method * @param args * @param extSelect * @return 查询结果,可能是实体类对象,List或者基础类型 */ private Object doSelect(Method method, Object[] args, ExtSelect extSelect) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException { //获取Sql语句 String sql = extSelect.value(); System.out.println("select sql:" + sql); //获取方法参数和Sql语句进行匹配 ConcurrentHashMap<String, Object> paramMap = getParamMap(method, args); //怕Sql参数顺序和@Param参数顺序不一致而把sql语句的参数放在一个有序的数组里 List<Object> sqlParamValue = new ArrayList<>(); List<String> sqlSelectParameter = SQLUtils.sqlSelectParameter(sql); for (String paramName : sqlSelectParameter) { Object paramValue = paramMap.get(paramName); sqlParamValue.add(paramValue); } //把参数替换成? sql = SQLUtils.parameQuestion(sql, sqlSelectParameter); System.out.println("new sql:"+sql); //执行JDBC ResultSet resultSet = JDBCUtils.query(sql, sqlParamValue); //判断是否有结果集 if (!resultSet.next()) { return null; } resultSet.previous(); //使用反射获取方法类型 Class<?> returnType = method.getReturnType(); //使用反射机制实例化对象 Object result = returnType.newInstance(); //遍历这个结果集 while (resultSet.next()) { for (String paramName : sqlSelectParameter) { //获取参数的值 Object resultValue = resultSet.getObject(paramName); //使用反射机制赋值 Field field = returnType.getDeclaredField(paramName); field.setAccessible(true); field.set(result, resultValue); } } return result; } 复制代码
在使用上面的代码时,我会检测方法上是否有 @Select
注解,有的话说明这个方法是用于查询语句的,我们就把这个注解传进来
我们改写UserMapper类,增加Select方法,如下
public interface UserMapper { @ExtInsert("insert into user(username,password) values (#{userName},#{password})") int insertUser(@ExtParam("userName") String userName, @ExtParam("password") String password); @ExtSelect("select * from user where username=#{userName} and password=#{password}") User selectUser(@ExtParam("userName") String userName, @ExtParam("password") String password); } 复制代码
改写测试用的代码,如下
public class Cluster { public static void main(String[] args) { UserMapper userMapper = SqlSession.getMapper(UserMapper.class); User user = userMapper.selectUser("name", "123"); System.out.println(user.getUserName()); } } 复制代码
执行后我的运行结果如下
附:整个核心的代理类如下
/** * @author libi * 用于动态代理,获取方法的参数并且给返回值 */ public class InvocationHandlerMybatis implements InvocationHandler { /** * @param proxy 代理对象 * @param method 拦截的方法 * @param args 方法上的参数 * @return 方法的返回值 * @throws Throwable */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("开始代理"); //判断方法上是否存在Insert注解 ExtInsert extInsert = method.getDeclaredAnnotation(ExtInsert.class); if (extInsert != null) { //执行插入的操作,返回影响行数 return doInsert(method, args, extInsert); } //判断方法上是否有Select注解 ExtSelect extSelect = method.getDeclaredAnnotation(ExtSelect.class); if (extSelect != null) { //执行查询的操作,返回实际实体类或者List return doSelect(method, args, extSelect); } return null; } /** * 执行插入的操作 * @param method * @param args * @param extInsert * @return 影响行数 */ private int doInsert(Method method, Object[] args, ExtInsert extInsert) { //获取Sql语句 String sql = extInsert.value(); System.out.println("insert sql:" + sql); //获取方法参数和Sql语句进行匹配 ConcurrentHashMap<String, Object> paramMap = getParamMap(method, args); //怕Sql参数顺序和@Param参数顺序不一致而把sql语句的参数放在一个有序的数组里 List<Object> sqlParamValue = new ArrayList<>(); String[] sqlInsertParameter = SQLUtils.sqlInsertParameter(sql); for (String paramName : sqlInsertParameter) { Object paramValue = paramMap.get(paramName); sqlParamValue.add(paramValue); } //把参数替换成? sql = SQLUtils.parameQuestion(sql, sqlInsertParameter); System.out.println("new sql:"+sql); //执行JDBC return JDBCUtils.insert(sql, false, sqlParamValue); } /** * 执行查询的操作 * @param method * @param args * @param extSelect * @return 查询结果,可能是实体类对象,List或者基础类型 */ private Object doSelect(Method method, Object[] args, ExtSelect extSelect) throws SQLException, IllegalAccessException, InstantiationException, NoSuchFieldException { //获取Sql语句 String sql = extSelect.value(); System.out.println("select sql:" + sql); //获取方法参数和Sql语句进行匹配 ConcurrentHashMap<String, Object> paramMap = getParamMap(method, args); //怕Sql参数顺序和@Param参数顺序不一致而把sql语句的参数放在一个有序的数组里 List<Object> sqlParamValue = new ArrayList<>(); List<String> sqlSelectParameter = SQLUtils.sqlSelectParameter(sql); for (String paramName : sqlSelectParameter) { Object paramValue = paramMap.get(paramName); sqlParamValue.add(paramValue); } //把参数替换成? sql = SQLUtils.parameQuestion(sql, sqlSelectParameter); System.out.println("new sql:"+sql); //执行JDBC ResultSet resultSet = JDBCUtils.query(sql, sqlParamValue); //判断是否有结果集 if (!resultSet.next()) { return null; } resultSet.previous(); //使用反射获取方法类型 Class<?> returnType = method.getReturnType(); //使用反射机制实例化对象 Object result = returnType.newInstance(); //遍历这个结果集 while (resultSet.next()) { for (String paramName : sqlSelectParameter) { //获取参数的值 Object resultValue = resultSet.getObject(paramName); //使用反射机制赋值 Field field = returnType.getDeclaredField(paramName); field.setAccessible(true); field.set(result, resultValue); } } return result; } /** * 建立方法上的参数和值@Param参数名的映射 * @param method * @param args * @return */ private ConcurrentHashMap<String, Object> getParamMap(Method method, Object[] args) { //定义一个Map,Key是参数名,Value是参数值 ConcurrentHashMap<String, Object> map = new ConcurrentHashMap<>(); //获取方法上的参数 Parameter[] parameters = method.getParameters(); for (int i = 0; i < parameters.length; i++) { //获取参数名称和参数的值 ExtParam param = parameters[i].getDeclaredAnnotation(ExtParam.class); if (param != null) { String name = param.value(); Object value = args[i]; System.out.println("paramName:"+name+",paramValue:"+value); map.put(name, value); } } return map; } } 复制代码
以上所述就是小编给大家介绍的《手写源码(四):自己实现Mybatis》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:- 手写源码(三):自己实现SpringMVC
- 手写源码(二):自己实现SpringIOC
- 手写源码(一):自己实现Spring事务
- EventBus 源码详细分析:手写 EventBus 核心代码
- 阿里架构师手写Tomcat——Session源码解析
- node进阶——之事无巨细手写koa源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。