JDBC 和连接池
1 JDBC概述
Java DataBase Connectivity,Java数据库连接,一种执行SQL的Java API,为多种关系数据库提供统一访问规范,由一组Java语言编写的类和接口组成。
数据库驱动:各个数据库生产商提供的JDBC实现类。使用统一的JDBC规范,不用专门去了解各个数据库的驱动API。
JDBC 可做三件事:与数据库建立连接、发送SQL、处理结果。
2 JDBC常用方法及增删改查
2.1 JDBC的开发步骤
先导入对应数据库的jar包 mysql-connector-java-5.0.8-bin.jar
创建lib目录,用于存放当前项目需要的所有jar包
选择jar包,右键执行build path / Add to Build Path
- 加载驱动
- 获得连接
- 获得语句执行平台statement
- 执行sql
- 处理结果
- 释放资源
import org.junit.Test; public class JDBCDemo1 { @Test public void demo1() throws Exception{ // 1.加载驱动 Class.forName("com.mysql.jdbc.Driver"); // 2.获得连接 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/web_test3", "root", "abc"); //url如果连接的是本机的路径,可以简化为jdbc:mysql:///web_test3 (3个///) // 3.基本操作:执行SQL // 3.1获得执行SQL语句的对象 Statement statement = conn.createStatement(); // 3.2编写SQL语句: String sql = "select * from user"; // 3.3执行SQL: ResultSet rs = statement.executeQuery(sql); // 3.4遍历结果集: while(rs.next()){ System.out.print(rs.getInt("id")+" "); System.out.print(rs.getString("username")+" "); System.out.print(rs.getString("password")+" "); System.out.print(rs.getString("nickname")+" "); System.out.print(rs.getInt("age")); System.out.println(); } // 4.释放资源 rs.close(); statement.close(); conn.close(); } }
2 JDBC常用类/接口:
DriverManager类:注册JDBC驱动,获取Connection对象(数据库链接对象,每个Connection代表一个物理连接会话)。
Class.forName("com.mysql.jdbc.Driver");
Connection接口:获取执行SQL的Statement对象,并包含多个用于控制事务的方法。
Statement createStatement() throws SQLException; //返回一个Statement对象,用来将SQL语句发送到数据库; PreparedStatement prepareStatement(String sql)throws SQLException; //返回预编译的Statement对象,即将SQL语句提交到数据库进行预编译; CallableStatement prepareCall(String sql) throws SQLException; //返回CallableStatement对象,用于调用存储过程。 //以上都返回用于执行sql语句的Statement对象,PreparedStatement和CallableStatement是Statement的子类,只有获得了Statement之后才可以执行sql语句; void setAutoCommit(boolean autoCommit) throws SQLException; //关闭自动提交,打开事务; void rollback() throws SQLException; //回滚事务,并释放Connection对象当前持有的数据库锁; void commit() throws SQLException; //提交事务,并释放Connection对象当前持有的数据库锁; void setTransactionIsolation(int level) throws SQLException; //设置事务的隔离级别; Savepoint setSavepoint() throws SQLException; //创建一个保存点; Savepoint setSavepoint(String name) throws SQLException; //以指定名字来创建一个保存点; void rollback(Savepoint savepoint) throws SQLException; //将事务回滚到指定的保存点;
Statement接口:用于执行静态SQL语句并返回它所生成结果的对象。
ResultSet executeQuery(String sql) throws SQLException;//执行查询语句,返回查询结果对应ResultSet对象。该方法只能用于执行查询语句。 int executeUpdate(String sql) throws SQLException;//执行inset/update/delete语句,返回受影响的行数;也可以执行create/alter/drop/truncate语句,并返回0; boolean execute(String sql) throws SQLException;//执行任意sql语句。若执行后第一个结果为ResultSet对象,则返回true; //若执行后第一个结果为受影响的行数或无结果,则返回false; void addBatch(String sql);//将给定的 SQL 命令添加到此 Statement 对象的当前命令列表中。 void clearBatch();//清空此Statement对象当前的SQL命令列表。 int[] executeBatch();//将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。
注意:如果 addBatch() -> executeBatch() 很慢,需要启动批处理操作rewriteBatchedStatements=true,即在数据库连接URL后面加上这个参数:String dbUrl = "jdbc:mysql://localhost:3306/User? rewriteBatchedStatements=true";该参数将多条sql分成若干个报文发送到mysql服务器,并在最后加上commit操作。
ResultSet接口:结果集对象,包含查询结果,可以通过列索引或列名获得列数据。
boolean next();//将光标从当前位置向前移动一行 int getInt(int columnIndex);//获取结果集当前行指定列 int getInt(String columnLabel);//获取结果集当前行指定列 long getLong(int columnIndex);//获取结果集当前行指定列 long getLong(String columnLabel);//获取结果集当前行指定列 String getString(int columnIndex);//获取结果集当前行指定列 String getString(String columnLabel);//获取结果集当前行指定列 //示例代码: while(resultSet.next()){ System.out.println(resultSet.getInt("id")) System.out.println(resultSet.getString("ename")) System.out.println(resultSet.getString("nickname")) }
3 JDBC的资源释放
JDBC程序执行结束后,需要将与数据库进行交互的对象释放掉,通常是ResultSet,Statement,Connection。尤其是Connection对象,一定要做到晚创建,早释放。
如果把所有关闭语句写在同一个try块里面,一旦前面的关闭语句抛异常,后面的关闭语句就无法执行,所以要给每个关闭语句一个try块。
最后为了保证资源能够释放,还有在每个关闭语句的后面加一个finally,在finally里给要关闭的资源赋值为空。这样即使关闭过程中抛异常不能及时关闭,但是由于赋值为空,没有引用该资源,在垃圾回收的时候也能够回收。
if(rs !=null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); }finally{ rs = null; } } if(statement !=null){ try { statement.close(); } catch (SQLException e) { e.printStackTrace(); }finally{ statement = null; } } if(conn !=null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); }finally{ conn = null; } }
2.2 JDBC的增删改查
public void demo1(){ Connection conn = null; Statement stmt = null; try{ Class.forName("com.mysql.jdbc.Driver"); conn = DriverManager.getConnection("jdbc:mysql:///web_test3", "root", "abc"); stmt = conn.createStatement(); //增加一条数据 String sql = "insert into user values (null,'eee','123','阿黄',21)"; int num = stmt.executeUpdate(sql); if(num > 0){ System.out.println("保存用户成功!!!"); } /* //删除、修改类似 //查询一条或多条多条 String sql = "select * from user"; rs = stmt.executeQuery(sql); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password")); } */ }catch(Exception e){ e.printStackTrace(); }finally{ // 资源释放:略 } }
3 JDBC的工具类的抽取
public class JDBCUtils { private static final String driverClassName; private static final String url; private static final String username; private static final String password; // 获取属性文件中的内容: static{ driverClassName="com.mysql.jdbc.Driver"; url="jdbc:mysql:///web_test3"; username="root"; password="abc"; } /* static{ // 获取属性文件中的内容: Properties properties = new Properties(); try { properties.load(new FileInputStream("src/db.properties")); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } driverClassName=properties.getProperty("driverClassName"); url=properties.getProperty("url"); username=properties.getProperty("username"); password=properties.getProperty("password"); } */ /** * 注册驱动的方法 */ public static void loadDriver(){ try { Class.forName(driverClassName); } catch (ClassNotFoundException e) { e.printStackTrace(); } } /** * 获得连接的方法 */ public static Connection getConnection(){ Connection conn = null; try{ // 将驱动一并注册: loadDriver(); // 获得连接 conn = DriverManager.getConnection(url,username, password); }catch(Exception e){ e.printStackTrace(); } return conn; } /** * 释放资源的方法 */ public static void release(Statement stmt,Connection conn){ if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } stmt = null; } if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } public static void release(ResultSet rs,Statement stmt,Connection conn){ // 资源释放: if(rs != null){ try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } rs = null; } if(stmt != null){ try { stmt.close(); } catch (SQLException e) { e.printStackTrace(); } stmt = null; } if(conn != null){ try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } conn = null; } } }
测试工具类:
public void demo1(){ Connection conn = null; Statement stmt = null; ResultSet rs = null; try{ conn = JDBCUtils.getConnection(); stmt = conn.createStatement(); String sql = "select * from user"; rs = stmt.executeQuery(sql); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("username")+" "+rs.getString("password")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs, stmt, conn); } }
4 用 properties 文件配置JDBC信息
1 开发中获得连接的4个参数(驱动、URL、用户名、密码)通常都存在配置文件中,方便后期维护,程序如果需要更换数据库,只需要修改配置文件即可。
使用properties文件要求:
1.文件位置:任意,建议src下
2.文件内容:一行一组数据,格式是“key=value”
a)key命名自定义,如果是多个单词,习惯使用点分隔。例如:jdbc.driver
b)value值不支持中文,如果需要使用非英文字符,将进行unicode转换
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/mydb user=root password=root
2 加载properties配置文件
public class JDBCUtils { private static String driver; private static String url; private static String user; private static String password; // 静态代码块 static { try { // 1 使用Properties处理流 // 使用load()方法加载指定的流 Properties props = new Properties(); Reader is = new FileReader("db.properties"); props.load(is); // 2 使用getProperty(key),通过key获得需要的值, driver = props.getProperty("driver"); url = props.getProperty("url"); user = props.getProperty("user"); password = props.getProperty("password"); } catch (Exception e) { throw new RuntimeException(e); } } /** * 获得连接 */ public static Connection getConnection() { try { // 1 注册驱动 Class.forName(driver); // 2 获得连接 Connection conn = DriverManager.getConnection(url, user, password); return conn; } catch (Exception e) { throw new RuntimeException(e); } } }
5 SQL 注入
输入一个存在的用户名 userName,再输入任意密码:XXX' OR 'a' = 'a'时,
SELECT * FROM Table WHERE Name = 'userName' AND PassWord = 'XXX' OR 'a' = 'a';
此时绕过了密码校验成功登录,这便是SQL注入问题。
为此,可以使用PreparedStatement将SQL预先进行编译,使用?作为占位符。当传入变量(包含SQL的关键字)时,不会识别 or 这些关键字。
public class UserDao { public boolean login(String username,String password){ Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; // 定义一个变量: boolean flag = false; try{ conn = JDBCUtils.getConnection(); String sql = "select * from user where username = ? and password = ?"; // 预编译SQL pstmt = conn.prepareStatement(sql); // 设置参数: pstmt.setString(1, username); pstmt.setString(2, password); rs = pstmt.executeQuery(); if(rs.next()){ flag = true; } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs, pstmt, conn); } return flag; }
6 JDBC批处理
Statement接口:void addBatch(String sql)
PreparedStatement接口重写了addBatch()方法:void addBatch()
public void demo1(){ Connection conn = null; Statement stmt = null; try{ // 获得连接: conn = JDBCUtils.getConnection(); // 创建执行批处理对象: stmt = conn.createStatement(); // 编写一批SQL语句: String sql1 = "create database test1"; String sql2 = "use test1"; String sql3 = "create table user(id int primary key auto_increment,name varchar(20))"; String sql4 = "insert into user values (null,'aaa')"; String sql5 = "insert into user values (null,'bbb')"; String sql6 = "insert into user values (null,'ccc')"; String sql7 = "update user set name = 'mmm' where id = 2"; String sql8 = "delete from user where id = 1"; // 添加到批处理 stmt.addBatch(sql1); stmt.addBatch(sql2); stmt.addBatch(sql3); stmt.addBatch(sql4); stmt.addBatch(sql5); stmt.addBatch(sql6); stmt.addBatch(sql7); stmt.addBatch(sql8); // 执行批处理: stmt.executeBatch(); }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(stmt, conn); } } //批量插入(使用PreparedStatement) @Test /** * 批量插入记录: * * 默认情况下MySQL批处理没有开启的,需要在url后面拼接一个参数即可。 */ public void demo2(){ // 记录开始时间: long begin = System.currentTimeMillis(); Connection conn = null; PreparedStatement pstmt = null; try{ conn = JDBCUtils.getConnection(); String sql = "insert into user values (null,?)"; pstmt = conn.prepareStatement(sql); for(int i=1;i<=10000;i++){ pstmt.setString(1, "name"+i); pstmt.addBatch(); if(i % 1000 == 0){ pstmt.executeBatch(); pstmt.clearBatch(); } } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(pstmt, conn); } long end = System.currentTimeMillis(); System.out.println((end-begin)); }
7 JDBC事务管理
例如在转账操作中,如果没有添加事务管理,可能出现 a b 账户互相转账后,两账户总额跟操作前不相等的情况,此时需要给转账功能添加事务管理。
public void demo1(){ Connection conn = null; PreparedStatement pstmt = null; try{ conn = JDBCUtils.getConnection(); // 1 开启事务 conn.setAutoCommit(false); String sql = "update account set money = money + ? where name = ?"; pstmt = conn.prepareStatement(sql); pstmt.setDouble(1, -1000); pstmt.setString(2, "aaa"); pstmt.executeUpdate(); int i = 1 / 0; pstmt.setDouble(1, 1000); pstmt.setString(2, "bbb"); pstmt.executeUpdate(); // 2 提交事务: conn.commit(); }catch(Exception e){ // 3 回滚事务: try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } e.printStackTrace(); }finally{ JDBCUtils.release(pstmt, conn); } }
8 DBUtils简介
为简化JDBC开发,可以使用DBUtils,DBUtils封装了对JDBC的操作,是JDBC的简化开发工具包。使用需要导入commons-dbutils-1.6.jar。
Dbutils三个核心功能
QueryRunner 提供对sql语句操作的API,用来执行SQL语句的对象
ResultSetHandler接口 定义select后怎样封装结果集.
DbUtils类 工具类,定义了关闭资源与事务处理的方法
1 QueryRunner
无事务管理:
int update(String sql,Object… args);:增、删、改
T query(String sql,ResultSetHandler rsh,Object… args);:查询
有事务管理:
int update(Connection conn, String sql, Object... params) :增、删、改
T query(Connection conn, String sql, ResultSetHandler<T> rsh, Object... params) :查询
query(Connection con,String sql,ResultSetHandler r, Object..params)
ResultSetHandler r 结果集的处理方式,传递ResultSetHandler接口实现类
Object..params SQL语句中的?占位符
注意: query方法返回值,返回的是T 泛型, 具体返回值类型,跟随结果集处理方式变化
//增加 public void demo1() throws SQLException{ // 创建核心类:QueryRunner: QueryRunner queryRunner = new QueryRunner(JDBCUtils2.getDataSource()); queryRunner.update("insert into account values (null,?,?)", "ddd",10000); } //删除 public void demo3() throws SQLException{ QueryRunner queryRunner = new QueryRunner(JDBCUtils2.getDataSource()); queryRunner.update("delete from account where id = ?", 3); } //修改 public void demo2() throws SQLException{ QueryRunner queryRunner = new QueryRunner(JDBCUtils2.getDataSource()); queryRunner.update("update account set name=?,money=? where id =?", "eee",20000,4); }
//增加 public void insert(){ try { //获取一个QueryRunner对象,用来执行SQL语句 QueryRunner queryRunner = new QueryRunner(); String sql = "INSERT INTO zhangwu(name,money,parent) VALUES(?,?,?)"; Object[] params = {"股票收入", 5500, "收入"}; Connection conn = JDBCUtils.getConnection(); int line = queryRunner.update(conn,sql,params);// 用来完成表数据的增加、删除、更新操作 //结果集处理 System.out.println("line = " + line); } catch (SQLException e) { throw new RuntimeException(e); } } //删除 public void delete(){ try { QueryRunner queryRunner = new QueryRunner(); String sql = "DELETE FROM zhangwu WHERE name = ?"; Object[] params = {"股票收入"}; Connection conn = JDBCUtils.getConnection(); int line = queryRunner.update(conn, sql, params); System.out.println("line="+line); } catch (SQLException e) { throw new RuntimeException(e); } } //修改 public void update(){ try { QueryRunner queryRunner = new QueryRunner(); String sql = "UPDATE zhangwu SET money = money+1000 WHERE name=?"; Object[] params = {"股票收入"}; Connection conn = JDBCUtils.getConnection(); int line = queryRunner.update(conn, sql, params); System.out.println("line="+line); } catch (SQLException e) { throw new RuntimeException(e); } }
QueryRunner的两个构造方法
第一种:不带connection参数
QueryRunner queryRunner = new QueryRunner();
这种情况下,调用update或query方法时,需要传入对应的connection参数
queryRunner.update(conn, sql,params);
conn.close();
DBUtils调用这种带connection参数的方法时,
只会关闭preparedstatement和resultset对象,不会关闭conneciton对象,
一些情况下,没有手动关闭,可能会导致连接池满了,访问数据库是处于一直等待的状态。
就是为了其他方法来调用这个conneciton,所以这种连接数据库的方法适合操作事务。
第二种:带connection参数
QueryRunner queryRunner = new QueryRunner(dataSource);
将dataSource传递进去,这样update或query方法内部就调用this.getconnection方法来从这个数据源获得连接,
queryRunner.update( sql,params);
操作完后,就关闭conneciton,preparedstatement和resultset对象.
事务是自动控制的,一条SQL语句一个事务,不需要人为的控制。
2 ResultSetHandler
ArrayHandler 将结果集的第一条记录封装到Object[]数组中,数组中的每一个元素就是这条记录中每一个字段的值
ArrayListHandler 将结果集的每一条记录都封装到Object[]数组中,数组封装到List集合中。
BeanHandler 将结果集第一条记录封装到javaBean中。
BeanListHandler 将结果集每一条记录封装到javaBean中,javaBean封装到List集合中
ColumnListHandler 将结果集指定的列的字段值,封装到一个List集合中
ScalarHandler 用于单数据。例如select count(*) from 表操作。
MapHandler 将结果集第一行封装到Map集合中,Key 列名, Value 该列数据
MapListHandler 将结果集第一行封装到Map集合中,Key 列名, Value 该列数据,Map集合存储到List集合
1)JavaBean
JavaBean就是一个类,在开发中常用封装数据。具有如下特性
1.需要实现接口:java.io.Serializable ,通常实现接口这步骤省略了,不会影响程序。
2.提供私有字段:private 类型 字段名;
3.提供getter/setter方法:
4.提供无参构造
示例:
public class ZhangWu { private int id; private String name; private double money; private String parent; public ZhangWu() { super(); } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public double getMoney() { return money; } public void setMoney(double money) { this.money = money; } public String getParent() { return parent; } public void setParent(String parent) { this.parent = parent; } @Override public String toString() { //该方法可以省略 return "ZhangWu [id=" + id + ", name=" + name + ", money=" + money + ", parent=" + parent + "]"; } }
3 ResultSetHandler使用案例
public class ArrayHandlerDemo { public void method(){ try { //获取QueryRunner对象 QueryRunner qr = new QueryRunner(); //执行SQL语句 String sql = "SELECT * FROM zhangwu"; Object[] params = {}; Connection conn = JDBCUtils.getConnection(); Object[] objArray = qr.query(conn, sql, new ArrayHandler(), params); //结果集的处理 System.out.println( Arrays.toString(objArray) ); conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } //ArrayListHandlerDemo String sql = "SELECT * FROM zhangwu WHERE money>?"; Object[] params = {2000}; Connection conn = JDBCUtils.getConnection(); List<Object[]> list = qr.query(conn, sql, new ArrayListHandler(), params); for (Object[] objArray : list) { System.out.println( Arrays.toString(objArray) ); } //BeanHandlerDemo String sql = "SELECT * FROM zhangwu WHERE id=?"; Object[] params = {1}; Connection conn = JDBCUtils.getConnection(); ZhangWu zw = qr.query(conn, sql, new BeanHandler<ZhangWu>(ZhangWu.class), params); System.out.println(zw); //BeanListHandlerDemo String sql = "SELECT * FROM zhangwu WHERE money>?"; Object[] params = {2000}; Connection conn = JDBCUtils.getConnection(); List<ZhangWu> list = qr.query(conn, sql, new BeanListHandler<ZhangWu>(ZhangWu.class), params); for (ZhangWu zw : list) { System.out.println(zw); } //ColumnListHandlerDemo String sql = "SELECT name FROM zhangwu WHERE money>?"; Object[] params = {2000}; Connection conn = JDBCUtils.getConnection(); List<String> list = qr.query(conn, sql, new ColumnListHandler<String>(), params); for (String str : list) { System.out.println(str); } //ScalarHandlerDemo String sql = "SELECT MAX(money) FROM zhangwu"; Object[] params = {}; Connection conn = JDBCUtils.getConnection(); Double max = qr.query(conn, sql, new ScalarHandler<Double>(), params); System.out.println("max=" + max);
/* * 结果集第一种处理方法, ArrayHandler * 将结果集的第一行存储到对象数组中 Object[] */ public static void arrayHandler()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT * FROM sort"; //调用方法query执行查询,传递连接对象,SQL语句,结果集处理方式的实现类 //返回对象数组 Object[] result = qr.query(con, sql, new ArrayHandler()); for(Object obj : result){ System.out.print(obj); } } /* * 结果集第二种处理方法,ArrayListHandler * 将结果集的每一行,封装到对象数组中, 出现很多对象数组 * 对象数组存储到List集合 */ public static void arrayListHandler()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT * FROM sort"; //调用query方法,结果集处理的参数上,传递实现类ArrayListHandler //方法返回值 每行是一个对象数组,存储到List List<Object[]> result= qr.query(con, sql, new ArrayListHandler()); //集合的遍历 for( Object[] objs : result){ //遍历对象数组 for(Object obj : objs){ System.out.print(obj+" "); } System.out.println(); } } /* * 结果集第三种处理方法,BeanHandler * 将结果集的第一行数据,封装成JavaBean对象 * 注意: 被封装成数据到JavaBean对象, Sort类必须有空参数构造 */ public static void beanHandler()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT * FROM sort "; //调用方法,传递结果集实现类BeanHandler //BeanHandler(Class<T> type) Sort s = qr.query(con, sql, new BeanHandler<Sort>(Sort.class)); System.out.println(s); } /* * 结果集第四种处理方法, BeanListHandler * 结果集每一行数据,封装JavaBean对象 * 多个JavaBean对象,存储到List集合 */ public static void beanListHander()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT * FROM sort "; //调用方法query,传递结果集处理实现类BeanListHandler List<Sort> list = qr.query(con, sql, new BeanListHandler<Sort>(Sort.class)); for(Sort s : list){ System.out.println(s); } } /* * 结果集第五种处理方法,ColumnListHandler * 结果集,指定列的数据,存储到List集合 * List<Object> 每个列数据类型不同 */ public static void columnListHandler()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT * FROM sort "; //调用方法 query,传递结果集实现类ColumnListHandler //实现类构造方法中,使用字符串的列名 List<Object> list = qr.query(con, sql, new ColumnListHandler<Object>("sname")); for(Object obj : list){ System.out.println(obj); } } /* * 结果集第六种处理方法,ScalarHandler * 对于查询后,只有1个结果 */ public static void scalarHandler()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT COUNT(*) FROM sort"; //调用方法query,传递结果集处理实现类ScalarHandler long count = qr.query(con, sql, new ScalarHandler<Long>()); System.out.println(count); } /* * 结果集第七种处理方法,MapHandler * 将结果集第一行数据,封装到Map集合中 * Map<键,值> 键:列名 值:这列的数据 */ public static void mapHandler()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT * FROM sort"; //调用方法query,传递结果集实现类MapHandler //返回值: Map集合,Map接口实现类, 泛型 Map<String,Object> map = qr.query(con, sql, new MapHandler()); //遍历Map集合 for(String key : map.keySet()){ System.out.println(key+".."+map.get(key)); } } /* * 结果集第八种处理方法,MapListHandler * 将结果集每一行存储到Map集合,键:列名,值:数据 * Map集合过多,存储到List集合 */ public static void mapListHandler()throws SQLException{ QueryRunner qr = new QueryRunner(); String sql = "SELECT * FROM sort"; //调用方法query,传递结果集实现类MapListHandler //返回值List集合, 存储的是Map集合 List<Map<String,Object>> list = qr.query(con, sql, new MapListHandler()); //遍历集合list for( Map<String,Object> map : list ){ for(String key : map.keySet()){ System.out.print(key+"..."+map.get(key)); } System.out.println(); } }
9 连接池
9.1 DBCP连接池
Java提供了数据库连接池公共接口:javax.sql.DataSource,各厂商需要让自己的连接池实现这个接口,这样程序可以方便地切换不同厂商的连接池。
DBCP是tomcat内置开源连接池,提供了DataSource接口的实现类:BasicDataSource类
需要的jar包:
mysql-connector-java-5.1.37-bin.jar:数据库驱动
commons-dbutils-1.6.jar:提供QueryRunner类方便进行增删改查操作
commons-dbcp-1.4.jar:
commons-pool-1.5.6.jar:提供高效的数据库连接池技术
BasicDataSource类的使用:
//使用DBCP实现数据库的连接池 public class JDBCUtils{ //创建出BasicDataSource类对象 private static BasicDataSource datasource = new BasicDataSource(); static{ datasource.setDriverClassName("com.mysql.jdbc.Driver"); datasource.setUrl("jdbc:mysql://localhost:3306/day33_user"); datasource.setUsername("root"); datasource.setPassword("123"); datasource.setInitialSize(10);//初始化的连接数 datasource.setMaxActive(8);//最大连接数量 datasource.setMaxIdle(5);//最大空闲数 datasource.setMinIdle(1);//最小空闲 } //定义静态方法,返回BasicDataSource类的对象 public static DataSource getDataSource(){ return datasource; } }
常见配置
必须项
driverClassName 数据库驱动名称
url 数据库的地址
username 用户名
password 密码
基本项(扩展)
maxActive 最大连接数量
minIdle 最小空闲连接
maxIdle 最大空闲连接
initialSize 初始化连接
测试连接池:
/* * 测试写好的工具类, * 提供的是一个DataSource接口的数据源 * QueryRunner类构造方法,接收DataSource接口的实现类 * 后面,调用方法update,query,无需传递他们Connection连接对象 */ public class QueryRunnerDemo{ public static void main(String[] args) { select(); } //定义2个方法,实现数据表的添加,数据表查询 //QueryRunner类对象,写在类成员位置 private static QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource()); //数据表查询 public static void select(){ String sql = "SELECT * FROM sort"; try{ List<Object[]> list = qr.query(sql, new ArrayListHandler()); for(Object[] objs : list){ for(Object obj : objs){ System.out.print(obj+"\t"); } System.out.println(); } }catch(SQLException ex){ throw new RuntimeException("数据查询失败"); } } }
9.2 C3P0连接池
C3P0是一个开源JDBC连接池,它实现了数据源和JNDI绑定,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。
C3P0与DBCP区别:DBCP没有自动回收空闲连接的功能,C3P0有
C3P0连接池的使用:
/** * 1 手动设置参数的方式: */ public void demo1(){ Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try{ // 获得连接:从连接池中获取: // 创建连接池: ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 设置连接参数: dataSource.setDriverClass("com.mysql.jdbc.Driver"); dataSource.setJdbcUrl("jdbc:mysql:///web_test4"); dataSource.setUser("root"); dataSource.setPassword("abc"); conn = dataSource.getConnection(); String sql = "select * from account"; pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("name")+" "+rs.getDouble("money")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs, pstmt, conn); } }
/** * 2 采用配置文件的方式: */ public void demo2(){ Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try{ // 获得连接:从连接池中获取: // 创建连接池://创建连接池默认去类路径下查找c3p0-config.xml ComboPooledDataSource dataSource = new ComboPooledDataSource(); // 从连接池中获得连接: conn = dataSource.getConnection(); String sql = "select * from account"; pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("name")+" "+rs.getDouble("money")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs, pstmt, conn); } }
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <!-- c3p0-config.xml --> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///web_test4</property> <property name="user">root</property> <property name="password">abc</property> <property name="initialPoolSize">5</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> </c3p0-config>
C3P0详细配置:
<?xml version="1.0" encoding="gbk"?> <c3p0-config> <!--默认配置,如果获取连接池时没有指定名称,则使用默认配置信息--> <default-config> <!--连接url--> <property name="jdbcUrl">jdbc:mysql://localhost:3306/mydb1</property> <!--数据库驱动类--> <property name="driverClass">com.mysql.jdbc.Driver</property> <!--用户名。Default: null--> <property name="user">root</property> <!--密码。Default: null--> <property name="password"></property> <!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 3 --> <property name="acquireIncrement">3</property> <!--定义在从数据库获取新连接失败后重复尝试的次数。Default: 30 --> <property name="acquireRetryAttempts">30</property> <!--两次连接中间隔时间,单位毫秒。Default: 1000 --> <property name="acquireRetryDelay">1000</property> <!--连接关闭时默认将所有未提交的操作回滚。Default: false --> <property name="autoCommitOnClose">false</property> <!--c3p0将建一张名为Test的空表,并使用其自带的查询语句进行测试。如果定义了这个参数那么 属性preferredTestQuery将被忽略。你不能在这张Test表上进行任何操作,它将只供c3p0测试 使用。Default: null--> <property name="automaticTestTable">Test</property> <!--获取连接失败将会引起所有等待连接池来获取连接的线程抛出异常。但是数据源仍有效 保留,并在下次调用getConnection()的时候继续尝试获取连接。如果设为true,那么在尝试 获取连接失败后该数据源将申明已断开并永久关闭。Default: false--> <property name="breakAfterAcquireFailure">false</property> <!--当连接池用完时客户端调用getConnection()后等待获取新连接的时间,超时后将抛出 SQLException,如设为0则无限期等待。单位毫秒。Default: 0 --> <property name="checkoutTimeout">100</property> <!--通过实现ConnectionTester或QueryConnectionTester的类来测试连接。类名需制定全路径。 Default: com.mchange.v2.c3p0.impl.DefaultConnectionTester--> <property name="connectionTesterClassName"></property> <!--指定c3p0 libraries的路径,如果(通常都是这样)在本地即可获得那么无需设置,默认null即可 Default: null--> <property name="factoryClassLocation">null</property> <!--Strongly disrecommended. Setting this to true may lead to subtle and bizarre bugs. (文档原文)作者强烈建议不使用的一个属性--> <property name="forceIgnoreUnresolvedTransactions">false</property> <!--每60秒检查所有连接池中的空闲连接。Default: 0 --> <property name="idleConnectionTestPeriod">60</property> <!--初始化时获取三个连接,取值应在minPoolSize与maxPoolSize之间。Default: 3 --> <property name="initialPoolSize">3</property> <!--最大空闲时间,60秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 --> <property name="maxIdleTime">60</property> <!--连接池中保留的最大连接数。Default: 15 --> <property name="maxPoolSize">15</property> <!--JDBC的标准参数,用以控制数据源内加载的PreparedStatements数量。但由于预缓存的statements 属于单个connection而不是整个连接池。所以设置这个参数需要考虑到多方面的因素。 如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default: 0--> <property name="maxStatements">100</property> <!--maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default: 0 --> <property name="maxStatementsPerConnection"></property> <!--c3p0是异步操作的,缓慢的JDBC操作通过帮助进程完成。扩展这些操作可以有效的提升性能 通过多线程实现多个操作同时被执行。Default: 3--> <property name="numHelperThreads">3</property> <!--当用户调用getConnection()时使root用户成为去获取连接的用户。主要用于连接池连接非c3p0 的数据源时。Default: null--> <property name="overrideDefaultUser">root</property> <!--与overrideDefaultUser参数对应使用的一个参数。Default: null--> <property name="overrideDefaultPassword">password</property> <!--定义所有连接测试都执行的测试语句。在使用连接测试的情况下这个一显著提高测试速度。注意: 测试的表必须在初始数据源的时候就存在。Default: null--> <property name="preferredTestQuery">select id from test where id=1</property> <!--用户修改系统配置参数执行前最多等待300秒。Default: 300 --> <property name="propertyCycle">300</property> <!--因性能消耗大请只在需要的时候使用它。如果设为true那么在每个connection提交的 时候都将校验其有效性。建议使用idleConnectionTestPeriod或automaticTestTable 等方法来提升连接测试的性能。Default: false --> <property name="testConnectionOnCheckout">false</property> <!--如果设为true那么在取得连接的同时将校验连接的有效性。Default: false --> <property name="testConnectionOnCheckin">true</property> <!--早期的c3p0版本对JDBC接口采用动态反射代理。在早期版本用途广泛的情况下这个参数 允许用户恢复到动态反射代理以解决不稳定的故障。最新的非反射代理更快并且已经开始 广泛的被使用,所以这个参数未必有用。现在原先的动态反射与新的非反射代理同时受到 支持,但今后可能的版本可能不支持动态反射代理。Default: false--> <property name="usesTraditionalReflectiveProxies">false</property> </default-config> <!--指定配置名称的配置信息--> <named-config name="dumbTestConfig"> </named-config> </c3p0-config>
9.3 Druid连接池
阿里旗下开源连接池产品,使用简单,可以与Spring框架进行快速整合
1 Druid的使用:
/** * 1 手动设置参数的方式 */ public void demo1(){ Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try{ // 使用连接池: DruidDataSource dataSource = new DruidDataSource(); // 手动设置数据库连接的参数: dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql:///web_test4"); dataSource.setUsername("root"); dataSource.setPassword("abc"); // 获得连接:旧方式 //conn = JDBCUtils.getConnection(); conn = dataSource.getConnection(); String sql = "select * from account"; pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("name")+" "+rs.getDouble("money")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs, pstmt, conn); } }
/** * 2 配置方式设置参数 * Druid配置方式可以使用属性文件配置的。 * 文件名称没有规定但是属性文件中的key要一定的。 */ public void demo2(){ Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try{ // 使用连接池: // 从属性文件中获取: Properties properties = new Properties(); properties.load(new FileInputStream("src/druid.properties")); DataSource dataSource = DruidDataSourceFactory.createDataSource(properties); // 获得连接:旧方式 //conn = JDBCUtils.getConnection(); conn = dataSource.getConnection(); String sql = "select * from account"; pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("name")+" "+rs.getDouble("money")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs, pstmt, conn); } }
2 Druid监控及慢sql记录:
https://www.cnblogs.com/telwanggs/p/7484854.html
9.4 自定义连接池
步骤:
1 编写一个类实现DataSource接口
2 重写getConnection方法
3 初始化多个连接在内存中
4 编写归还连接的方法
//自定义连接池 public class MyDataSource implements DataSource { // 将一些连接存入到内存中,可以定义一个集合,用于存储连接对象。 private List<Connection> connList = new ArrayList<Connection>(); // 在初始化的时候提供一些连接 public MyDataSource() { // 初始化连接: for(int i = 1;i<=3;i++){ // 向集合中存入连接: connList.add(JDBCUtils.getConnection()); } } // 从连接池中获得连接的方法 @Override public Connection getConnection() throws SQLException { Connection conn = connList.remove(0); // 增强连接: MyConnectionWrapper connWrapper = new MyConnectionWrapper(conn, connList); return connWrapper; } // 编写一个归还连接的方法: /*public void addBack(Connection conn){ connList.add(conn); }*/ @Override public PrintWriter getLogWriter() throws SQLException { // TODO Auto-generated method stub return null; } @Override public int getLoginTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { // TODO Auto-generated method stub return null; } @Override public void setLogWriter(PrintWriter arg0) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int arg0) throws SQLException { // TODO Auto-generated method stub } @Override public boolean isWrapperFor(Class<?> arg0) throws SQLException { // TODO Auto-generated method stub return false; } @Override public <T> T unwrap(Class<T> arg0) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Connection getConnection(String arg0, String arg1) throws SQLException { // TODO Auto-generated method stub return null; } }
测试自定义连接池:
public class DataSourceDemo1 { @Test /** * 测试自定义连接池 */ public void demo1(){ Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; DataSource dataSource = null; try{ // 获得连接: // conn = JDBCUtils.getConnection(); // 从连接池中获得连接: dataSource = new MyDataSource(); conn = dataSource.getConnection(); String sql = "select * from account"; pstmt = conn.prepareStatement(sql); rs = pstmt.executeQuery(); while(rs.next()){ System.out.println(rs.getInt("id")+" "+rs.getString("name")+" "+rs.getDouble("money")); } }catch(Exception e){ e.printStackTrace(); }finally{ JDBCUtils.release(rs, pstmt, conn); // 归还连接: // dataSource.addBack(conn); } } }
使用装饰者增强Connection中的close方法:将原有的close方法的逻辑改为归还(增强一个类中的方法)
装饰者模式使用条件:
1、增强类和被增强类实现相同的接口
2、在增强的类中获得被增强的类的引用
public class MyConnectionWrapper extends ConnectionWrapper{ private Connection conn; private List<Connection> connList; public MyConnectionWrapper(Connection conn,List<Connection> connList) { super(conn); this.conn = conn; this.connList= connList; } // 增强某个方法: @Override public void close() throws SQLException { // super.close(); // 归还连接: connList.add(conn); } }
JDBC 和连接池的更多相关文章
- c3p0、dbcp、tomcat jdbc pool 连接池配置简介及常用数据库的driverClass和驱动包
[-] DBCP连接池配置 dbcp jar包 c3p0连接池配置 c3p0 jar包 jdbc-pool连接池配置 jdbc-pool jar包 常用数据库的driverClass和jdbcUrl ...
- jdbc数据连接池dbcp要导入的jar包
jdbc数据连接池dbcp要导入的jar包 只用导入commons-dbcp-x.y.z.jarcommons-pool-a.b.jar
- 关于JDBC和连接池我学到的(转载保存)
1.JDBC数据库连接池的必要性 在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤: 在主程序(如servlet.beans)中建立数据库连接. 进行sql操作 断开数据库连接. 这种模 ...
- JDBC之 连接池
JDBC之 连接池 有这样的一种现象: 用java代码操作数据库,需要数据库连接对象,一个用户至少要用到一个连接.现在假设有成千上百万个用户,就要创建十分巨大数量的连接对象,这会使数据库承受极大的压力 ...
- JDBC数据源连接池(4)---自定义数据源连接池
[续上文<JDBC数据源连接池(3)---Tomcat集成DBCP>] 我们已经 了解了DBCP,C3P0,以及Tomcat内置的数据源连接池,那么,这些数据源连接池是如何实现的呢?为了究 ...
- JDBC数据源连接池(3)---Tomcat集成DBCP
此文续<JDBC数据源连接池(2)---C3P0>. Apache Tomcat作为一款JavaWeb服务器,内置了DBCP数据源连接池.在使用中,只要进行相应配置即可. 首先,确保Web ...
- JDBC数据源连接池(2)---C3P0
我们接着<JDBC数据源连接池(1)---DBCP>继续介绍数据源连接池. 首先,在Web项目的WebContent--->WEB-INF--->lib文件夹中添加C3P0的j ...
- DBCP,C3P0与Tomcat jdbc pool 连接池的比较
hibernate开发组推荐使用c3p0; spring开发组推荐使用dbcp(dbcp连接池有weblogic连接池同样的问题,就是强行关闭连接或数据库重启后,无法reconnect,告诉连接被重置 ...
- JDBC数据源连接池的配置和使用实例
个人学习参考所用,勿喷! 使用JDBC建立数据库连接的两种方式: 1.在代码中使用DriverManager获得数据库连接.这种方式效率低,并且其性能.可靠性和稳定性随着用户访问量得增加逐渐下降. 2 ...
- mysql,jdbc、连接池
show processlist; select * from information_schema.processlist; Command: The type of command the thr ...
随机推荐
- MyBatis3 入门学习指南
官网原文:http://www.mybatis.org/mybatis-3/zh/index.html 1.简介 1.1 什么是 MyBatis? MyBatis 是一款优秀的持久层框架,它支持定制化 ...
- TensorFlow与caffe中卷积层feature map大小计算
刚刚接触Tensorflow,由于是做图像处理,因此接触比较多的还是卷及神经网络,其中会涉及到在经过卷积层或者pooling层之后,图像Feature map的大小计算,之前一直以为是与caffe相同 ...
- TCP 协议简析
TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的.可靠的.基于字节流的通信协议,数据在传输前要建立连接,传输完毕后还要断开连接.它是个超级麻烦的协议, ...
- SQL Server 2016新特性:列存储索引新特性
SQL Server 2016新特性:列存储索引新特性 行存储表可以有一个可更新的列存储索引,之前非聚集的列存储索引是只读的. 非聚集的列存储索引支持筛选条件. 在内存优化表中可以有一个列存储索引,可 ...
- 凭什么相信你,我的CNN模型
背景 学术界一直困惑的点是"如何让看似黑盒的CNN模型说话",即对它的分类结果给出解释. 这里的解释是指,让模型告诉我们它是通过图片的哪些像素做出判断的,并不是深度学习理论层面的解 ...
- 微信小程序——购物车结算
项目需要做个购物车结算功能,先分析需求: 1.全选,反选的功能.当选中的个数 = 购物车的数量时,勾选全选按钮,反之则取消选中全选按钮: 2.改变选中状态时,计算总价和总数量: 3.单个产品的数量加减 ...
- ifconfig 中的 eth0 eth0:1 eth0.1 与 lo
1. eth0 eth0:1 eth0.1 eth0 eth0:1 和eth0.1三者的关系对应于物理网卡.子网卡.虚拟VLAN网卡的关系:物理网卡:物理网卡这里指的是服务器上实际的网络接口设备,这里 ...
- js 零散知识总结
网页播放声音 这个非常简单,我们只需要在html和js设置即可.首先看html代码 html代码 <audio id="sound" autoplay="autop ...
- 21备忘录模式Memento
一.什么是备忘录模式 Memento模式也叫备忘录模式,是行为模式之 一,它的作用是保存对象的内部状态,并在需要 的时候(undo/rollback)恢复对象以前的状态. 二.备忘录模式的应用场景 如 ...
- Mysql Window 解压版卸载
windows如何彻底卸载mysql 如何彻底删除mysql 1.首先在windows服务中将mysql服务删掉,使用命令 sc delete mysql 2.在控制面板中卸载掉mysql. 3.清理 ...