利用反射和代理简单模拟mybatis实现简单的CRUD

Posted 北山情韵 情韵博客

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用反射和代理简单模拟mybatis实现简单的CRUD相关的知识,希望对你有一定的参考价值。

利用反射接口做java数据库操作

今天突发奇想,好像一些基本的CRUD操作路数都是一样的,又想到mybatis中的操作,便想着简单的模拟一下。随便写写,就当练习反射了。

Dao接口类:

这里使用泛型,是为了更好的对数据进行处理

public interface BaseDao<T> 

    // 获取所有信息
    List<T> getAll();

    // 根据id查询信息
    T getById(int id);

    // 根据id修改信息
    int updateById(T t);

    // 根据id删除信息
    int deleteById(int id);

    // 插入数据
    int insert(T t);
    

之前一直在犹豫,是否可以对接口创建代理类,后来查阅了一些资料,发现mybatis好像就是对接口的一些代理的处理。

使用JDK自带的代理接口InvocationHandler:

 public class ProxyGen<T> implements InvocationHandler 
	 // 要代理的接口
     private Class<T> aClass;
	// 要处理的表名
     private String tableName;
	// pojo的字节码文件
     private Class objClass;
	// 完整的构造方法
     public ProxyGen(Class<T> interfaceClazz, String tableName, Class objClass) 
         this.aClass = interfaceClazz;
         this.tableName = tableName;
         this.objClass = objClass;
     

     @Override
     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
   		...... // 代码处理  
  	 
     
     // 获取代理类
     public T getProxy() 
         return (T) Proxy.newProxyInstance(aClass.getClassLoader(), new Class[]				aClass, this);
     
  

下面展示一下利用反射做简单的CRUD:

先做一些简单的提前准备:

 // 一些连接信息
Connection connect = null;
ResultSet rs = null;
PreparedStatement ps = null;

// 这时候通过反射拿到类的属性信息
try 
	// 获取连接
	connect = SqlUtil.getConnect();
 catch (Exception e) 
	throw new RuntimeException(e);

把这些先声明出来,方便后续的管理和使用。

查询全部数据:

// 获取所有的学生信息
if (method.getName().equalsIgnoreCase("getAll")) 
    // 存储查询到的所有信息
    List list = new ArrayList<>();
    // 这里执行结果
    ps = connect.prepareStatement("select * from " + tableName);
    // 查询结果 进行封装
    rs = ps.executeQuery();
    // 按照顺序进行赋值
    while (rs.next()) 
        // 获取到所有的字段 进行反射赋值 必须确保有序 !
        List<Field> fields = getClassFileds(objClass);
        // 获取类对象
        Object objClazz = objClass.getConstructor(null).newInstance(null);
        // 赋值完毕
        // 利用了反射出来的字符是有序的,这样保证了数据库的字段顺序和反射出来的字段顺序一致
        for (int i = 0; i < fields.size(); i++) 
            Object object = rs.getObject(i + 1);
            fields.get(i).setAccessible(true);
            fields.get(i).set(objClazz, object);
        

        list.add( objClazz);

    
	// 关闭连接
    SqlUtil.close(connect, ps, rs);

    return list;

根据id查询信息:

// 根据id查询信息
 if (method.getName().equalsIgnoreCase("getById")) 
 	// 先查看输入的参数是否获取到
	 System.out.println("args = " + Arrays.toString(args));
	 // 获取执行结果
	 ps = connect.prepareStatement("select * from " + tableName + " where id = " + args[0]);
	 // 获取执行结果
	 rs = ps.executeQuery();
	 // 获取类对象信息
	 Object obj = objClass.getConstructor(null).newInstance();
	 // 获取POJO类的字段信息
	 List<Field> fileds = getClassFileds(objClass);
	 while (rs.next()) 
		 // 循环赋值
		 for (int i = 0; i < fileds.size(); i++) 
		 	// 因为字段是私有的 所以需要加上这一步
			 fileds.get(i).setAccessible(true);
			 // 给字段赋值
			 fileds.get(i).set(obj, rs.getObject(i + 1));
		 
	 
	 // 释放资源
	 SqlUtil.close(connect, ps, rs);
	 // 返回对象
	 return obj;
 
 
  /**
  * 获取类反射字段
  *
  * @param objClass
  * @return
  */
 private List<Field> getClassFileds(Class objClass) 
	 ArrayList<Field> list = new ArrayList<>();
	 // 获取当前类的所有的字段
	 Field[] fields = objClass.getDeclaredFields();
	 for (Field field : fields) 
		 list.add(field);
	 
	 return list;
 

根据Id修改信息:

既然是反射,那就得把数据写活,如果直接用pojo类的字段和属性,那不是写死了?

// 根据id修改信息
 /**
  * 这个比较特殊 传入的对象是一个student对象
  */
 if (method.getName().equalsIgnoreCase("updateById")) 
	 // 拿到传输过来的对象
	 Object obj = args[0];
	 // 做一个自适应 如果值为null就不修改
	 Field[] fields = obj.getClass().getDeclaredFields();
	 // 拼接字符串
	 StringBuilder builder = new StringBuilder();
	 builder.append("update ").append(tableName).append(" ").append("set ");
	 // 循环获取值
	 for (Field field : fields) 
		 field.setAccessible(true);
		 Object o = field.get(obj);

		 if (o != null && !field.getName().equalsIgnoreCase("id")) 
			 // 这里需要注意 如果对象为时间 需要转换一下
			 if (o instanceof Date) 
				 o = String.format("%tF", (Date) o);
			 
			 // 继续拼接
              builder
                    .append(field.getName())
                    .append("=")
                    .append("\'")
                    .append(o)
                    .append("\'")
                    .append(",");
		 
	 

	 // 拼接sql 如果拼接完最后一个字符为[,],需要去掉
	 String sql = builder.toString().endsWith(",") ? builder.deleteCharAt(builder.length() - 1).toString() : builder.toString();

	 // 主键id单独 处理
	 for (Field field : fields) 
		 field.setAccessible(true);
		 Object o = field.get(obj);

		 if (field.getName().equalsIgnoreCase("id") && o != null) 
			 sql += " where id = \'" + o + "\'";
		 

	 


	 System.out.println("sql = " + sql);
	 // 执行对象
	 ps = connect.prepareStatement(sql);
	 // 获取执行结果
	 int i = ps.executeUpdate();

	 SqlUtil.close(connect, ps, rs);

	 return i;

	 
 

根据id修改对象:

/**
  * 根据id删除对象
  */
 if (method.getName().equalsIgnoreCase("deleteById")) 
     // 获取参数
	 Integer arg = (Integer) args[0];
     // 数据判断 避免空指针异常
	 if (arg != null) 
         // 直接拼接删除sql语句
		 String sql = "delete from  " + tableName + " where id = " + arg;
		 System.out.println("sql = " + sql);
		 ps = connect.prepareStatement(sql);
		 int i = ps.executeUpdate();
		 SqlUtil.close(connect, ps, rs);
		 return i;
	  else 
		 return 0;
	 
 

插入数据:

 // 插入数据
 if (method.getName().equalsIgnoreCase("insert")) 
	 // 传入的对象
	 Object obj = args[0];
	 // 根据传入的对象进行拼接sql
	 Field[] fields = obj.getClass().getDeclaredFields();
	 // 拼接字符串
	 StringBuilder builder = new StringBuilder();
	 builder.append("insert into ").append(tableName).append(" ").append(" set ");
	 for (Field field : fields) 
		 field.setAccessible(true);
		 Object o = field.get(obj);
		 if (o != null) 
			 // 这里需要注意 如果对象为时间 需要转换一下
			 if (o instanceof Date) 
				 o = String.format("%tF", (Date) o);
			 
			 builder.append(field.getName()).append("=\'").append(o).append("\',");
		 

	 
	 // 拼接sql 如果拼接完最后一个字符为[,],需要去掉
	 String sql = builder.toString().endsWith(",") ? builder.deleteCharAt(builder.length() - 1).toString() : builder.toString();

	 ps = connect.prepareStatement(sql);

	 int i = ps.executeUpdate();
	 SqlUtil.close(connect, ps, rs);
	 return i;

 

简单测试了一下,还不错。

以上是关于利用反射和代理简单模拟mybatis实现简单的CRUD的主要内容,如果未能解决你的问题,请参考以下文章

简单版Mybatis框架的实现

简单实现动态代理(Proxy)

MyBatis的简单使用

手撸MyBatis从配置文件到读出数据库的模拟实现

mybatis动态代理

ORM简介 && MyBatis和Hibernate的不同 && 动态代理简单实现Mybatis基本使用