1.数据库连接池
   什么是数据库连接池
  简单来说:数据库连接池就是提供连接的。。。
   为什么我们要使用数据库连接池
     - 数据库的连接的建立和关闭是非常消耗资源的
- 频繁地打开、关闭连接造成系统性能低下
编写连接池
     - 编写连接池需实现java.sql.DataSource接口
- 创建批量的Connection用LinkedList保存【既然是个池,当然用集合保存、、LinkedList底层是链表,对增删性能较好】
- 实现getConnetion(),让getConnection()每次调用,都是在LinkedList中取一个Connection返回给用户
- 调用Connection.close()方法,Connction返回给LinkedList
       private static LinkedList<Connection> list = new LinkedList<>();          //获取连接只需要一次就够了,所以用static代码块     static {         //读取文件配置         InputStream inputStream = Demo1.class.getClassLoader().getResourceAsStream("db.properties");          Properties properties = new Properties();         try {             properties.load(inputStream);             String url = properties.getProperty("url");             String username = properties.getProperty("username");             String driver = properties.getProperty("driver");             String password = properties.getProperty("password");              //加载驱动             Class.forName(driver);              //获取多个连接,保存在LinkedList集合中             for (int i = 0; i < 10; i++) {                 Connection connection = DriverManager.getConnection(url, username, password);                 list.add(connection);             }                      } catch (IOException e) {             e.printStackTrace();         } catch (ClassNotFoundException e) {             e.printStackTrace();         } catch (SQLException e) {             e.printStackTrace();         }      }      //重写Connection方法,用户获取连接应该从LinkedList中给他     @Override     public Connection getConnection() throws SQLException {         System.out.println(list.size());         System.out.println(list);         //先判断LinkedList是否存在连接        return list.size() > 0 ? list.removeFirst() : null;      }    
  我们已经完成前三步了,现在问题来了**。我们调用Conncetion.close()方法,是把数据库的物理连接关掉,而不是返回给LinkedList的**
  解决思路:
     - 写一个Connection子类,覆盖close()方法
- 写一个Connection包装类,增强close()方法
- 用动态代理,返回一个代理对象出去,拦截close()方法的调用,对close()增强
分析第一个思路:
     - Connection是通过数据库驱动加载的,保存了数据的信息。写一个子类Connection,new出对象,子类的Connction无法直接继承父类的数据信息,也就是说子类的Connection是无法连接数据库的,更别谈覆盖close()方法了。
分析第二个思路:
     - 写一个Connection包装类。         - 写一个类,实现与被增强对象的相同接口【Connection接口】
- 定义一个变量,指向被增强的对象
- 定义构造方法,接收被增强对象
- 覆盖想增强的方法
- 对于不想增强的方法,直接调用被增强对象的方法
 
- 这个思路本身是没什么毛病的,就是实现接口时,方法太多了!,所以我们也不使用此方法
分析第三个思路代码实现:
       @Override     public Connection getConnection() throws SQLException {          if (list.size() > 0) {             final Connection connection = list.removeFirst();              //看看池的大小             System.out.println(list.size());              //返回一个动态代理对象             return (Connection) Proxy.newProxyInstance(Demo1.class.getClassLoader(), connection.getClass().getInterfaces(), new InvocationHandler() {                  @Override                 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {                      //如果不是调用close方法,就按照正常的来调用                     if (!method.getName().equals("close")) {                         method.invoke(connection, args);                     } else {                          //进到这里来,说明调用的是close方法                         list.add(connection);                          //再看看池的大小                         System.out.println(list.size());                      }                     return null;                 }              });         }         return null;     }   
  我们上面已经能够简单编写一个线程池了。下面我们来使用一下开源数据库连接池
   DBCP
  使用DBCP数据源的步骤:
     - 导入两个jar包【Commons-dbcp.jar和Commons-pool.jar】
- 读取配置文件
- 获取BasicDataSourceFactory对象
- 创建DataSource对象
     private static DataSource dataSource = null;      static {         try {             //读取配置文件             InputStream inputStream = Demo3.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");             Properties properties = new Properties();             properties.load(inputStream);              //获取工厂对象             BasicDataSourceFactory basicDataSourceFactory = new BasicDataSourceFactory();             dataSource = basicDataSourceFactory.createDataSource(properties);          } catch (IOException e) {             e.printStackTrace();         } catch (Exception e) {             e.printStackTrace();         }     }      public static Connection getConnection() throws SQLException {         return dataSource.getConnection();      }      //这里释放资源不是把数据库的物理连接释放了,是把连接归还给连接池【连接池的Connection内部自己做好了】     public static void release(Connection conn, Statement st, ResultSet rs) {          if (rs != null) {             try {                 rs.close();             } catch (Exception e) {                 e.printStackTrace();             }             rs = null;         }         if (st != null) {             try {                 st.close();             } catch (Exception e) {                 e.printStackTrace();             }          }         if (conn != null) {             try {                 conn.close();             } catch (Exception e) {                 e.printStackTrace();             }          }     }   
   C3P0
  C3P0数据源的性能更胜一筹,并且它可以使用XML配置文件配置信息!
  步骤:
     - 导入开发包【c3p0-0.9.2-pre1.jar】和【mchange-commons-0.2.jar】
- 导入XML配置文件【可以在程序中自己一个一个配,C3P0的doc中的Configuration有XML文件的事例】
- new出ComboPooledDataSource对象
     private static ComboPooledDataSource comboPooledDataSource = null;      static {         //如果我什么都不指定,就是使用XML默认的配置,这里我指定的是oracle的         comboPooledDataSource = new ComboPooledDataSource("oracle");     }      public static Connection getConnection() throws SQLException {         return comboPooledDataSource.getConnection();     }  
   Tomcat数据源
  Tomcat服务器也给我们提供了连接池,内部其实就是DBCP
  步骤:
     - 在META-INF目录下配置context.xml文件【文件内容可以在tomcat默认页面的 JNDI Resources下Configure Tomcat's Resource Factory找到】
- 导入Mysql或oracle开发包到tomcat的lib目录下
- 初始化JNDI->获取JNDI容器->检索以XXX为名字在JNDI容器存放的连接池
context.xml文件的配置:
   <Context>    <Resource name="jdbc/EmployeeDB"             auth="Container"             type="javax.sql.DataSource"                          username="root"             password="root"             driverClassName="com.mysql.jdbc.Driver"             url="jdbc:mysql://localhost:3306/zhongfucheng"             maxActive="8"             maxIdle="4"/> </Context>  
           try {  			//初始化JNDI容器             Context initCtx = new InitialContext();  			//获取到JNDI容器             Context envCtx = (Context) initCtx.lookup("java:comp/env");  			//扫描以jdbc/EmployeeDB名字绑定在JNDI容器下的连接池             DataSource ds = (DataSource)                     envCtx.lookup("jdbc/EmployeeDB");              Connection conn = ds.getConnection();             System.out.println(conn);          }    
   使用dbutils框架
  dbutils它是对JDBC的简单封装,极大简化jdbc编码的工作量
   DbUtils类
  提供了关闭连接,装载JDBC驱动,回滚提交事务等方法的工具类【比较少使用,因为我们学了连接池,就应该使用连接池连接数据库】
   QueryRunner类
  该类简化了SQL查询,配合ResultSetHandler使用,可以完成大部分的数据库操作,重载了许多的查询,更新,批处理方法。大大减少了代码量
   ResultSetHandler接口
  该接口规范了对ResultSet的操作,要对结果集进行什么操作,传入ResultSetHandler接口的实现类即可。
     - ArrayHandler:把结果集中的第一行数据转成对象数组。
- ArrayListHandler:把结果集中的每一行数据都转成一个数组,再存放到List中。
- BeanHandler:将结果集中的第一行数据封装到一个对应的JavaBean实例中。
- BeanListHandler:将结果集中的每一行数据都封装到一个对应的JavaBean实例中,存放到List里。
- ColumnListHandler:将结果集中某一列的数据存放到List中。
- KeyedHandler(name):将结果集中的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
- MapHandler:将结果集中的第一行数据封装到一个Map里,key是列名,value就是对应的值。
- MapListHandler:将结果集中的每一行数据都封装到一个Map里,然后再存放到List
- ScalarHandler 将ResultSet的一个列到一个对象中。
使用DbUtils框架对数据库的CRUD
    /* * 使用DbUtils框架对数据库的CRUD * 批处理 * * */ public class Test {      @org.junit.Test     public void add() throws SQLException {          //创建出QueryRunner对象         QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "INSERT INTO student (id,name) VALUES(?,?)";          //我们发现query()方法有的需要传入Connection对象,有的不需要传入         //区别:你传入Connection对象是需要你来销毁该Connection,你不传入,由程序帮你把Connection放回到连接池中         queryRunner.update(sql, new Object[]{"100", "zhongfucheng"});      }      @org.junit.Test     public void query()throws SQLException {          //创建出QueryRunner对象         QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "SELECT * FROM student";          List list = (List) queryRunner.query(sql, new BeanListHandler(Student.class));         System.out.println(list.size());      }      @org.junit.Test     public void delete() throws SQLException {         //创建出QueryRunner对象         QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "DELETE FROM student WHERE id='100'";          queryRunner.update(sql);     }      @org.junit.Test     public void update() throws SQLException {         //创建出QueryRunner对象         QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "UPDATE student SET name=? WHERE id=?";          queryRunner.update(sql, new Object[]{"zhongfuchengaaa", 1});     }      @org.junit.Test     public void batch() throws SQLException {         //创建出QueryRunner对象         QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "INSERT INTO student (name,id) VALUES(?,?)";          Object[][] objects = new Object[10][];         for (int i = 0; i < 10; i++) {             objects[i] = new Object[]{"aaa", i + 300};         }         queryRunner.batch(sql, objects);     }  }  
   
   分页
  分页技术是非常常见的,在搜索引擎下搜索页面,不可能把全部数据都显示在一个页面里边。所以我们用到了分页技术。
   Oracle实现分页
   	/* 	  Oracle分页语法: 	    @lineSize---每页显示数据行数 	    @currentPage----当前所在页 	 	*/ 	SELECT *FROM ( 	    SELECT 列名,列名,ROWNUM rn 	    FROM 表名 	    WHERE ROWNUM<=(currentPage*lineSize)) temp 	 	WHERE temp.rn>(currentPage-1)*lineSize;   
  Oracle分页原理简单解释:
   	/* 	  Oracle分页: 	    Oracle的分页依赖于ROWNUM这个伪列,ROWNUM主要作用就是产生行号。 	 	  分页原理: 	    1:子查询查出前n行数据,ROWNUM产生前N行的行号 	    2:使用子查询产生ROWNUM的行号,通过外部的筛选出想要的数据 	 	  例子: 	    我现在规定每页显示5行数据【lineSize=5】,我要查询第2页的数据【currentPage=2】 	    注:【对照着语法来看】 	 	  实现: 	    1:子查询查出前10条数据【ROWNUM<=10】 	    2:外部筛选出后面5条数据【ROWNUM>5】 		3:这样我们就取到了后面5条的数据 	*/  
   Mysql实现分页
   	/* 	  Mysql分页语法: 	  @start---偏移量,不设置就是从0开始【也就是(currentPage-1)*lineSize】 	  @length---长度,取多少行数据 	 	*/ 	SELECT * 	FROM 表名 	LIMIT [START], length; 	 	/* 	  例子: 	    我现在规定每页显示5行数据,我要查询第2页的数据 	 	  分析: 	    1:第2页的数据其实就是从第6条数据开始,取5条 	 	  实现: 	    1:start为5【偏移量从0开始】 	    2:length为5  */  
  总结:
     - Mysql从(currentPage-1)*lineSize开始取数据,取lineSize条数据
- Oracle先获取currentPage*lineSize条数据,从(currentPage-1)*lineSize开始取数据
使用JDBC连接数据库实现分页
  下面是常见的分页图片
   
  
   
  配合图片,看下我们的需求是什么:
     - 算出有多少页的数据,显示在页面上
- 根据页码,从数据库显示相对应的数据。
分析:
     - 算出有多少页数据这是非常简单的【在数据库中查询有多少条记录,你每页显示多少条记录,就可以算出有多少页数据了】
- 使用Mysql或Oracle的分页语法即可
通过上面分析,我们会发现需要用到4个变量
     - currentPage--当前页【由用户决定的】
- totalRecord--总数据数【查询表可知】
- lineSize--每页显示数据的数量【由我们开发人员决定】
- pageCount--页数【totalRecord和lineSize决定】
         //每页显示3条数据         int lineSize = 3;          //总记录数         int totalRecord = getTotalRecord();          //假设用户指定的是第2页         int currentPage = 2;          //一共有多少页         int pageCount = getPageCount(totalRecord, lineSize);          //使用什么数据库进行分页,记得要在JdbcUtils中改配置         List<Person> list = getPageData2(currentPage, lineSize);         for (Person person : list) {             System.out.println(person);         }      }      //使用JDBC连接Mysql数据库实现分页     public static List<Person> getPageData(int currentPage, int lineSize) throws SQLException {          //从哪个位置开始取数据         int start = (currentPage - 1) * lineSize;          QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "SELECT name,address  FROM person LIMIT ?,?";          List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{start, lineSize});         return persons;      }      //使用JDBC连接Oracle数据库实现分页     public static List<Person> getPageData2(int currentPage, int lineSize) throws SQLException {          //从哪个位置开始取数据         int start = (currentPage - 1) * lineSize;          //读取前N条数据         int end = currentPage * lineSize;          QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "SELECT " +                 "  name, " +                 "  address " +                 "FROM ( " +                 "  SELECT " +                 "    name, " +                 "    address , " +                 "    ROWNUM rn " +                 "  FROM person " +                 "  WHERE ROWNUM <= ? " +                 ")temp WHERE temp.rn>?";          List<Person> persons = (List<Person>) queryRunner.query(sql, new BeanListHandler(Person.class), new Object[]{end, start});         return persons;      }      public static int getPageCount(int totalRecord, int lineSize) {          //简单算法         //return (totalRecord - 1) / lineSize + 1;          //此算法比较好理解,把数据代代进去就知道了。         return totalRecord % lineSize == 0 ? (totalRecord / lineSize) : (totalRecord / lineSize) + 1;      }       public static int  getTotalRecord() throws SQLException {          //使用DbUtils框架查询数据库表中有多少条数据         QueryRunner queryRunner = new QueryRunner(JdbcUtils.getDataSource());         String sql = "SELECT COUNT(*) FROM person";          Object o = queryRunner.query(sql, new ScalarHandler());          String ss = o.toString();         int  s = Integer.parseInt(ss);         return s;     }   
     如果文章有错的地方欢迎指正,大家互相交流。习惯在微信看技术文章的同学,可以关注微信公众号:Java3y。