学习笔记——JDBC及对增删改查的封装

JDBC

JDBC流程:

首先需要导包(驱动、版本),创建lib文件夹,右键Add as Library

  1. 加载驱动

  2. 获取连接对象

  3. 获取执行的sql语句对象

  4. 执行sql语句

  5. 释放资源

资源的释放应当先开后关

public static void main(String[] args) {
   Connection conn = null;
   Statement st = null;
   // 1. 加载驱动
   try {
       Class.forName("com.mysql.jdbc.Driver");
       // 2. 获取连接对象
       conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123456");
       // 3. 获取执行sql语句对象
       st = conn.createStatement();
       // 4. 执行sql语句
       int j = st.executeUpdate("insert into student values(null,\'" + "南桐" + "\')");
       System.out.println(j);
       // int i = st.executeUpdate("insert into student values(null,'龙泽')");
       // 5. 释放资源 先开后关
  } catch (Exception e) {
       e.printStackTrace();
  } finally {
       try {
           if (st!=null){
               st.close();
          }
      } catch (SQLException throwables) {
           throwables.printStackTrace();
      }finally {
           try {
               if (conn!=null){
                   conn.close();
              }
          } catch (SQLException throwables) {
               throwables.printStackTrace();
          }
      }
  }
}

面试题

  • Collection,CollectionsConnection区别?

Collection是数组的实现类接口,提供了对集合对象进行基本操作的通用接口方法

Collections是集合类的一个工具类/帮助类,其中提供了一系列静态方法,用于对集合中元素进行排序、搜索以及线程安全等各种操作。

Connection是一个接口,主要用于实现应用程序和DBMS之间进行通信的一个通道,通过该通道可以向数据库服务器发送可以执行sql脚本,并且可以将执行结果返回到程序中,可以获取一个用于处理sql语句的执行器(statement),Connection就是一个程序与数据库之间会话(session)。

初步封装

对重复语句进行封装,简化之后的代码量

public class JDBCUtil {
   private static String driver = null;
   private static String url = null;
   private static String username = null;
   private static String password = null;
   static {
       Properties p = new Properties();
       try {
           // 读取properties中的配置文件
           p.load(JDBCUtil.class.getResourceAsStream("/db.properties"));
           driver = p.getProperty("driverClassName");
           url = p.getProperty("url");
           username = p.getProperty("username");
           password = p.getProperty("password");
      } catch (IOException e) {
           e.printStackTrace();
      }
  }

   public static Connection getConn(){
       try {
           Class.forName(driver);
           // 2. 获取连接对象
           return DriverManager.getConnection(url, username, password);
      } catch (Exception throwables) {
           throwables.printStackTrace();
      }
       return null;
  }

   public static void close(Statement st, Connection conn){
       try {
           if (st!=null){
               st.close();
          }
      } catch (SQLException throwables) {
           throwables.printStackTrace();
      }finally {
           try {
               if (conn!=null){
                   conn.close();
              }
          } catch (SQLException throwables) {
               throwables.printStackTrace();
          }
      }
  }

   public static void close(ResultSet rs,PreparedStatement ps, Connection conn){
       try {
           if (rs!=null){
               rs.close();
          }
      } catch (SQLException throwables) {
           throwables.printStackTrace();
      }finally {
           try {
               if (ps!=null){
                   ps.close();
              }
          } catch (SQLException throwables) {
               throwables.printStackTrace();
          } finally {
               try {
                   if (conn!=null){
                       conn.close();
                  }
              } catch (SQLException throwables) {
                   throwables.printStackTrace();
              }
          }
      }
  }
}

上述代码需要创建一个resources文件夹存放自己的配置文件。


增删改进行封装

public static int update(String sql) {
   int count = 0 ;
   Connection conn = null;
   Statement st = null;
   try {
       conn = JDBCUtil.getConn();
       st = conn.createStatement();
       count = st.executeUpdate(sql);
  } catch (Exception e) {
       e.printStackTrace();
  } finally {
       JDBCUtil.close(st, conn);
  }
   return count;  // 返回值为受影响的行数
}

使用时只需要:

String sql = "insert into student values(null,'猛1')";
System.out.println(JDBCUtil.update(sql));

查询语句的运用

public static void main(String[] args) {
   Connection conn = null;
   PreparedStatement ps = null;
   ResultSet rs = null;
   try {
       conn = JDBCUtil.getConn();
       ps = conn.prepareStatement("select * from student");
       rs = ps.executeQuery();
       ResultSetMetaData md = rs.getMetaData();
       int c = md.getColumnCount();
       while (rs.next()) {
           for (int i = 1; i <= c; i++) {
               System.out.print(rs.getString(i)+"\t");
          }
           System.out.println();
//           System.out.print(rs.getString("sid") + "\t");
//           System.out.println(rs.getString("name"));
      }
  } catch (Exception e) {
       e.printStackTrace();
  } finally {
       JDBCUtil.close(rs, ps, conn);
  }
}

注意

对于以上代码就会把所有数据给查询出来,不安全。

public static void main(String[] args) throws SQLException {
   String name = "dsad ' or 1 = 1 -- '";
   query("select * from student where name = '" + name + "'");
}

private static void query(String sql) throws SQLException {
   Connection conn = JDBCUtil.getConn();
   Statement st = conn.createStatement();
   ResultSet rs = st.executeQuery(sql);
   ArrayList list = new ArrayList();
   while (rs.next()) {
       int sid = rs.getInt(1);
       String name = rs.getString(2);
       list.add(new Student(sid, name));
  }
   list.forEach(System.out::println);
}

Statement有SQL注入风险,不推荐使用。针对这个的问题,jdbc中提供了一个用于对sql语句预编译的执行器:PrepredStatement,提供了占位符(?)的使用:允许将需要动态传入到sql语句的数据使用占位符代替,然后使用提供的setXXX()方法为占位符填充值,从而有效的避免SQL注入的风险。

​ ps = conn.prepareStatement(sql); //预处理操作:为占位符填充值(参数1:占位符的位置,参数2:具体值) ps.setString(1,name); ps.setString(2,pwd);

再封装(看这个就行)

中间陆陆续续修改太多次了,直接放最终的封装吧。

主要还是对查询的封装,传入字节码对象sql语句若干参数,主要是用反射遍历类的所有属性和方法,找到每个属性对应的set方法,执行invoke()执行这个方法,将每个对象存入list集合中,最后返回list集合。

关键代码:

t = clazz.newInstance();
Field[] df = clazz.getDeclaredFields();
Method[] dm = clazz.getDeclaredMethods();
for (Field field : df) {
   Object obj = rs.getObject(field.getName());
   for (Method method : dm) {
       String mname = method.getName().substring(3);
       if (method.getName().startsWith("set") &&(mname.substring(0,1).toLowerCase()+mname.substring(1)).equals(field.getName())) {
           method.invoke(t, obj);
      }
}
}
list.add(t);

总代码:

public class DSUtil {
   private static DataSource ds = null;

   static {
       Properties p = new Properties();
       try {
           p.load(Thread.currentThread().getContextClassLoader().getResourceAsStream("db.properties"));
           ds = BasicDataSourceFactory.createDataSource(p);
      } catch (Exception e) {
           e.printStackTrace();
      }
  }

   public static Connection getConn() {
       try {
           return ds.getConnection();
      } catch (Exception throwables) {
           throwables.printStackTrace();
      }
       return null;
  }

   public static void close(PreparedStatement ps, Connection conn) {
       try {
           if (ps != null) {
               ps.close();
          }
      } catch (SQLException throwables) {
           throwables.printStackTrace();
      } finally {
           try {
               if (conn != null) {
                   conn.close();
              }
          } catch (SQLException throwables) {
               throwables.printStackTrace();
          }
      }
  }

   public static void close(ResultSet rs, PreparedStatement ps, Connection conn) {
       try {
           if (rs != null) {
               rs.close();
          }
      } catch (SQLException throwables) {
           throwables.printStackTrace();
      } finally {
           try {
               if (ps != null) {
                   ps.close();
              }
          } catch (SQLException throwables) {
               throwables.printStackTrace();
          } finally {
               try {
                   if (conn != null) {
                       conn.close();
                  }
              } catch (SQLException throwables) {
                   throwables.printStackTrace();
              }
          }
      }
  }

   /**
    * 增删改工具类抽取
    */
   public static int execute(String sql, Object... args) {
       Connection conn = null;
       PreparedStatement ps = null;
       try {
           // 1. 加载驱动
           // 2. 获取连接对象
           conn = DSUtil.getConn();
           // 3. 获取执行sql语句对象
           ps = conn.prepareStatement(sql);
           for (int i = 1; i <= args.length; i++) {
               ps.setObject(i, args[i - 1]);
          }
           // 4. 执行sql语句
           return ps.executeUpdate();
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           // 5. 释放资源 先开后关
           DSUtil.close(ps, conn);
      }
       return 0;
  }

   /**
    * 查询语句的封装
    */
   public static <T> List<T> query(String sql, Class<T> clazz, Object... args) {
       Connection conn = null;
       PreparedStatement ps = null;
       ResultSet rs = null;
       List<T> list = new ArrayList<>();
       T t;
       try {
           conn = DSUtil.getConn();
           ps = conn.prepareStatement(sql);
           for (int i = 1; i <= args.length; i++) {
               ps.setObject(i, args[i - 1]);
          }
           rs = ps.executeQuery();
           while (rs.next()) {
               t = clazz.newInstance();
               Field[] df = clazz.getDeclaredFields();
               Method[] dm = clazz.getDeclaredMethods();
               for (Field field : df) {
                   Object obj = rs.getObject(field.getName());
                   for (Method method : dm) {
                       String mname = method.getName().substring(3);
                       if (method.getName().startsWith("set") && (mname.substring(0,1).toLowerCase()+mname.substring(1)).equals(field.getName())) {
                           method.invoke(t, obj);
                      }
                  }
              }
               list.add(t);
          }
           return list;
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           DSUtil.close(rs, ps, conn);
      }
       return list;
  }
}

其中主要还是对查询(query)的封装会比较难。除了上面的通过反射获取属性名从而调用他的set方法之外,我们还可以通过ResultSetMetaData中的getColumnLabel(i)获取数据库所有的列名,然后存放进map中,列名为键,值为值。

    public static <T> List<T> query(String sql, Class<T> clazz, Object... args) {
       Connection conn = null;
       PreparedStatement ps = null;
       ResultSet rs = null;
       List<T> list = new ArrayList<>();
       try {
           conn = DSUtil.getConn();
           ps = conn.prepareStatement(sql);
           for (int i = 1; i <= args.length; i++) {
               ps.setObject(i, args[i - 1]);
          }
           rs = ps.executeQuery();
           while (rs.next()) {
               T t = clazz.newInstance();
               HashMap<String, Object> hm = new HashMap<>();
               ResultSetMetaData rsm = rs.getMetaData();
               for (int i = 1; i <= rsm.getColumnCount(); i++) {
                   hm.put(rsm.getColumnLabel(i), rs.getObject(rsm.getColumnLabel(i)));
              }
               BeanUtils.populate(t, hm);
               list.add(t);
          }
           return list;
      } catch (Exception e) {
           e.printStackTrace();
      } finally {
           DSUtil.close(rs, ps, conn);
      }
       return list;
  }

BeanUtils

  1. 将一个对象中的内容复制到另外一个对象中,底层实现反射。

  2. 将map中的数据复制到对象中。(servlet)

    public static void main(String[] args) throws Exception {
       Stu s = new Stu();
       Map<String, Object> map = new HashMap<>();
       map.put("age", 19);
       map.put("name", "葬爱");
       BeanUtils.populate(s, map);
       System.out.println(s);
  }

BeanUtils.populate(s, map);就相当于数据迁移,map的key就是属性名,value就是值。


模拟实现

对底层代码的模拟实现:

public static void main(String[] args) throws Exception {
  Stu s = new Stu();
   
  Map<String, Object> map = new HashMap<>();
  map.put("age", 19);
  map.put("name", "葬爱");
  populate(s, map);
  System.out.println(s);
}

public static <T> void populate(T t, Map<String, Object> map) throws Exception {
  Set<String> set = map.keySet();
  Class<T> c = (Class<T>) t.getClass();
  for (String ss : set) {
      Method[] dm = c.getDeclaredMethods();
      for (Method method : dm) {
          if (method.getName().equals("set" + ss.substring(0, 1).toUpperCase() + ss.substring(1))) {
              method.invoke(t, map.get(ss));
          }
      }
  }
}

 

posted @ 2021-12-10 17:32  Black空我  阅读(70)  评论(0)    收藏  举报