# JDBCToMybatis **Repository Path**: darkfiregit/JDBCToMybatis ## Basic Information - **Project Name**: JDBCToMybatis - **Description**: a tool about devloping mybatis framework - **Primary Language**: Java - **License**: MulanPSL-2.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 1 - **Forks**: 0 - **Created**: 2023-01-28 - **Last Updated**: 2023-02-11 ## Categories & Tags **Categories**: Uncategorized **Tags**: Java, MyBatis ## README # 手撕JDBC框架 ## JDBC封装Mybatis ### Select 原生SQL语句 封装原生查询,返回ResultSet ```java public ResultSet select(String sql, Object... values){ try { Class.forName(driverPath); Connection connection = DriverManager.getConnection(sqlUrl,user,password); PreparedStatement pst = connection.prepareStatement(sql); for (int i =0;i T selectOne(String sql,RowMapper rm,Object... values){ T obj = null; //范型对象 try { Class.forName(driverPath); Connection connection = DriverManager.getConnection(sqlUrl,user,password); PreparedStatement pst = connection.prepareStatement(sql); for (int i =0;i T rowMapper(ResultSet set) throws SQLException{ //策略模式 int id = set.getInt(1); //获取ID String name = set.getString(2);//获取用户名 int age = set.getInt(3); User user = new User(id,name,age); return (T) user; } }; User user = SqlSession.getInstance().selectOne("select * from `user` where id = ?",rowMapper,0); System.out.println(user); } ``` 同理,如果查询结果包含多条,那么可以创建 List 接收范型集合。 ### Select 简化SQL调用 ​ 在进行SQL调用时,我们的代码通常会有很多重复的部分。那么我们该怎么简化SQL语句的执行呢。上面我们通过创建使用原生jdbc获取结果集后,通过RowMapper中的接口来将该结果集转化为实现RowMapper.rowMapper()方法中返回的对象。虽然简化了SQL的调用,但是仍然需要自己实现rowMapper匿名类。 ​ 于是我们可以在SqlSession,也就是在Dao层调用sql执行方法的那一层里可以实现对查询结果的封装: ```java ResultSet set = pst.executeQuery(); while (set.next()){ //对执行结果进行对象封装 obj = handler.mapperClass(set,cls); } ``` 我们创建了Handler结果集处理层,将实现对SQL执行结果的自动化封装成对象/Map集合/普通类型。 ​ `1.我们可以通过Map集合的方式,获取到ResultSet中的列名以及对应的数据,将该数据放入K-V键值对中` `2.我们可以传入Class,通过反射的形式获取到ResultSet中的列名对应的属性字段,然后再对其进行赋值即可。也可以通过反射的形式获取set方法来对属性值进行赋值。` ```java //通过反射的方法来获取cls对象的属性列表,在通过set集合的列名来给类设置属性值。 private Object toObject(ResultSet set,Class cls) throws SQLException { ResultSetMetaData rsm = set.getMetaData(); Object obj = null; try { obj = cls.newInstance(); for(int i = 1;i<=rsm.getColumnCount();i++){ // ············································ // 方法一 反射直接修改字段 // //通过反射获取类的字段(属性)信息 // Field field = obj.getClass().getDeclaredField(rsm.getColumnName(i)); // //设置私有属性是可修改的 // field.setAccessible(true); // //直接设置属性值 // field.set(obj,set.getObject(rsm.getColumnName(i))); // ············································· //通过反射获取类中的set方法 String methodName = "set"+rsm.getColumnName(i).substring(0,1).toUpperCase()+rsm.getColumnName(i).substring(1); System.out.println(methodName); Field field = cls.getDeclaredField(rsm.getColumnName(i));//根据属性名称获取单个属性 //通过属性的类型来调用类中的Setter方法,如setId(Integer.class); if (field.getGenericType().toString().equals("int")||field.getGenericType().toString().equals("class java.lang.Integer")){ Method method = obj.getClass().getDeclaredMethod(methodName,Integer.class); method.invoke(obj,set.getInt(rsm.getColumnName(i))); }else if(field.getGenericType().toString().equals("string")||field.getGenericType().toString().equals("class java.lang.String")){ Method method = obj.getClass().getDeclaredMethod(methodName,String.class); method.invoke(obj,set.getString(rsm.getColumnName(i))); } } } catch (Exception e) { e.printStackTrace(); } return obj; } ``` ​ ### Insert 简化SQL调用 ​ 一条插入SQL语句会包含需要传入的参数。一般使用PrepareStatement来对该SQL语句预处理后在对问号部位进行赋值。那么就需要自己传入参数而且参数必须和SQL语句问号位置相对应。 ```java //insert user values(?,?,?) PreparedStatement pst = getPst(sql); pst.setInt(1,1001); pst.setString(2,"刘德华"); pst.setInt(3,19); ``` ​ 如果SQL语句中问号数量太多,如果我们手动设置这些参数非常麻烦。那么我们就需要简化SQL的操作,看怎么才能让程序自动给处理在哪里插入参数。 ​ 思路:我们可以构造一个SQL语句如:select * from user where id = #{id},然后再传入一个User对象,该User的对象的id属性存在且不为空,那么可以通过传递sql语句和一个user对象即可完成语句的执行。我们可以通过StringBuilder对传入的SQL进行解析,解析完毕之后再使用反射获取到对象对应属性的值,随后使用原生jdbc对其进行预处理赋值执行即可。 ```java public List parseSQL(String sql){ List params = new ArrayList<>(); StringBuilder sb = new StringBuilder(sql); StringBuilder newSQL = new StringBuilder(); while (true){ int left = sb.indexOf("#{"); int right = sb.indexOf("}"); if(left!=-1 && right!=-1 && left list = handler.parseSQL(sql); //获取处理后的SQL语句 PreparedStatement pst = getPst((String) list.get(list.size()-1)); //反射对象获取对象的相应属性 for (int i = 0;i params) throws Exception { Class cls = obj.getClass(); if(cls == Integer.class || cls == String.class || cls == Float.class || cls == Double.class){ //说明传入的是一个基本参数,直接赋值 pst.setObject(1, obj); }else if(obj instanceof Map){ //说明传入的是Map对象 Map map = (Map) obj; for(int i = 0;i T getFuncProxy(Class cls){ //获得类的加载 ClassLoader classLoader = cls.getClassLoader(); Class[] interfaces = new Class[]{cls}; InvocationHandler invocationHandler = new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //获取返回类型的类 //method -> 代理类被调用的方法 //args -> 代理类被调用方法的参数 Class returnCLass = method.getReturnType(); System.out.println("returnType->"+returnCLass.getTypeName()); System.out.println("MethodName->"+method.getName()); if(String.class.equals(returnCLass)){ return "返回结果已经被代理了"; }else{ return null; } } }; //创建一个代理 Object obj = Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); return (T)obj; } ``` 测试方法 ```java package Proxy; public interface MyFunction { String read(); void write(); } @Test public void test(){ MyFunction mf = getFuncProxy(MyFunction.class); System.out.println(mf.read()); mf.write(); } ``` ### 实现代码 ```java public T getMapper(Class cls){ //此处提供代理Dao对象的功能,实现Dao层接口无需实现即可执行SQL语句的功能 ClassLoader classLoader = cls.getClassLoader(); Class[] interfaces = new Class[]{cls}; InvocationHandler invocationHandler = new InvocationHandler() { /*** * proxy -> 代理对象 * method ->代理对象的方法 * args -> 代理对象的方法的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Annotation an = method.getAnnotations()[0]; //通过被代理的方法获取该方法的注解名 Class aClass = an.annotationType();//获取注解类型的类 Method method1 = aClass.getMethod("value"); //通过反射该类调用value方法获取注解值 String sql = (String)method1.invoke(an); if (Insert.class.equals(aClass)) { SqlSession. getInstance().insert(sql,args[0]); } else if (Select.class.equals(aClass)) { //a.第一步要判断是不是list类型 System.out.println("log1->"+method.getReturnType()); if(method.getReturnType() == List.class){ //查询方法select2的参数为基本类型+Map+自定义类型, //所以需要获取被代理方法的返回类型才可以进行实体的映射创建 //如果返回类型为List,那么就需要获取List中的范型类用于MapperClass的实体映射。 Type wholeReturnType = method.getGenericReturnType(); //获取返回类型的全部->List ParameterizedType realReturnTypes = (ParameterizedType)(wholeReturnType); Type realType = realReturnTypes.getActualTypeArguments()[0]; System.out.println("getReturnType->"+method.getGenericReturnType() +",getGenericReturnType->"+wholeReturnType+",getActualTypeArguments->"+realType); if(args == null){ return SqlSession.getInstance().select2(sql,(Class) realType); }else{ return SqlSession.getInstance().select2(sql,(Class) realType,args[0]); } }else { //此时只需传入方法的返回值类型即可 if(args == null){ return SqlSession.getInstance().select2(sql,(Class) method.getReturnType()).get(0); }else{ return SqlSession.getInstance().select2(sql,(Class) method.getReturnType(),args[0]).get(0); } } } else if (Delete.class.equals(aClass)) { SqlSession.getInstance().delete(sql,args[0]); } else if (Update.class.equals(aClass)) { SqlSession.getInstance().update(sql,args[0]); }else { System.err.println("未被注解定义的类型!"); } return null; } }; //开始创建类的代理 Object obj = Proxy.newProxyInstance(classLoader,interfaces,invocationHandler); return (T)obj; } ``` ## 加入Druid数据库连接线程池 通过静态代码块的方式,让该代码在类被加载时就执行来创建一个Properties对象 ````java { properties = new Properties(); try { InputStream in = new BufferedInputStream(new FileInputStream("src/main/druid.properties")); properties.load(in); } catch (Exception e) { e.printStackTrace(); } } ```` 并由Druid自动创建线程池并获得 ```java public Connection getConnection() throws Exception { //通过druid工厂创建数据源 dataSource = DruidDataSourceFactory.createDataSource(properties); return dataSource.getConnection(); } ```