本节内容:
- JDBC连接池
- DBUtils
一、JDBC连接池
实际开发中“获得连接”或“释放资源”是非常消耗系统资源的两个过程,为了解决此类性能问题,通常情况我们采取连接池技术,来共享连接Connection。在程序开始的时候,可以创建几个连接,将连接放入到连接池中。用户使用连接的时候,可以从连接池中进行获取。用完之后,可以将连接归还连接池。
连接池主要解决的是性能问题。
1. 连接池概述
(1)概念
用池来管理Connection,这样可以重复使用Connection。有了池,我们就不用自己来创建Connection,而是通过池来获取Connection对象。当使用完Connection后,调用Connection的close()方法也不会真的关闭Connection,而是把Connection“归还”给池。池就可以再利用这个Connection对象了。
(2)规范
Java为数据库连接池提供了公共的接口,javax.sql.DataSource,各个厂商需要让自己的连接池实现这个接口。这样应用程序可以方便地切换不同厂商的连接池。
常见的连接池:DBCP、C3P0。还有tomcat自带的JNDI,JNDI使用的概率极低。市场上使用率最高的是C3P0。
2. 自定义连接池(了解)
定义一个连接池实现javax.sql.DataSource,使用List集合存放多个连接对象。如果我们需要编写自定义连接池,需要完成以下步骤:
- 创建连接池(作为数据源),需要实现接口javax.sql.DataDource。因为我们只使用该接口中getConnection()方法,简化本案例,我们只实现javax.sql.DataDource中的getConnection()方法,而没有实现javax.sql.DataDource中的其他接口
- 提供一个集合,用于存放连接,因为移除/添加操作过多,所以选择LinkedList
- 本案例在静态代码块中,为连接池初始化5个连接(本案例中初始化连接使用上一篇文章中实现的工具类)
- 之后程序如果需要连接,调用实现类的getConnection(),该方法将从连接池(容器List)获得连接。为了保证当前连接只能提供给一个线程使用,所以我们需要将连接先从连接池中移除。
- 当用户使用完连接,释放资源时,不执行close()方法,而是将连接添加到连接池中。
MyDataSource.java
package cn.itheima.jdbc.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.LinkedList; import java.util.logging.Logger; import javax.sql.DataSource; import cn.itheima.jdbc.utils.JDBCUtils_V3; public class MyDataSource implements DataSource{ //1.创建1个容器用于存储Connection对象 private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.创建5个连接放到容器中去 static{ for (int i = 0; i < 5; i++) { Connection conn = JDBCUtils_V3.getConnection(); pool.add(conn); } } /** * 重写获取连接的方法 */ @Override public Connection getConnection() throws SQLException { Connection conn = null; //3.使用前先判断 if(pool.size()==0){ //4.池子里面没有,我们再创建一些 for (int i = 0; i < 5; i++) { conn = JDBCUtils_V3.getConnection(); pool.add(conn); } } //5.从池子里面获取一个连接对象Connection conn = pool.remove(0); return conn; } /** * 归还连接对象到连接池中去 */ public void backConnection(Connection conn){ pool.add(conn); } @Override public PrintWriter getLogWriter() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @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 <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public Connection getConnection(String username, String password) throws SQLException { // TODO Auto-generated method stub return null; } }
TestMyDataSource.java
package cn.itheima.jdbc.test; import java.sql.Connection; import java.sql.PreparedStatement; import javax.sql.DataSource; import org.junit.Test; import cn.itheima.jdbc.DataSource.MyDataSource; import cn.itheima.jdbc.DataSource.MyDataSource1; import cn.itheima.jdbc.utils.JDBCUtils_V3; public class TestMyDataSource { /** * 添加用户 * 使用未改造过的connection */ @Test public void testAddUser() { Connection conn = null; PreparedStatement pstmt = null; // 1.创建自定义连接池对象 MyDataSource dataSource = new MyDataSource(); try { // 2.从池子中获取连接 conn = dataSource.getConnection(); String sql = "insert into user values(null,?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setString(1, "吕布"); pstmt.setString(2, "貂蝉"); int rows = pstmt.executeUpdate(); if (rows > 0) { System.out.println("添加成功!"); } else { System.out.println("添加失败!"); } } catch (Exception e) { throw new RuntimeException(e); } finally { dataSource.backConnection(conn); } } }
JDBCUtils_V3.java
package cn.itheima.jdbc.utils; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import java.util.ResourceBundle; /** * 提供获取连接和释放资源的 方法 * * @author Never Say Never * @date 2017年10月29日 * @version V1.0 */ public class JDBCUtils_V3 { private static String driver; private static String url; private static String username; private static String password; /** * 静态代码块加载配置文件信息 */ static { try { // 1.通过当前类获取类加载器 ClassLoader classLoader = JDBCUtils_V3.class.getClassLoader(); // 2.通过类加载器的方法获得一个输入流 InputStream is = classLoader.getResourceAsStream("db.properties"); // 3.创建一个properties对象 Properties props = new Properties(); // 4.加载输入流 props.load(is); // 5.获取相关参数的值 driver = props.getProperty("driver"); url = props.getProperty("url"); username = props.getProperty("username"); password = props.getProperty("password"); } catch (IOException e) { e.printStackTrace(); } } /** * 获取连接方法 * * @return */ public static Connection getConnection() { Connection conn = null; try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); } catch (Exception e) { e.printStackTrace(); } return conn; } /** * 释放资源方法 * * @param conn * @param pstmt * @param rs */ public static void release(Connection conn, PreparedStatement pstmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pstmt != null) { try { pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
3. 自定义连接池(方法增强)
上面的自定义连接池示例中存在严重问题,用户调用getConnection()获得连接后,必须使用backConnection(conn)方法进行连接的归还,如果用户调用conn.close()将连接真正的释放,连接池中将出现无连接可用。
我们希望用户即使调用了close()方法,连接仍归还给连接池。我们需要对close()方法进行增强,从而实现将连接归还给连接池的功能。
(1)方法增强的方式
- 继承:子类继承父类,将父类的方法进行重写,从而进行增强
- 使用前提:必须有父类,且存在继承关系。
- 装饰者设计模式,此设计模式专门用于增强方法
- 使用前提:必须有接口
- 缺点:需要将接口的所有方法都实现
- 动态代理:在运行时动态的创建代理类,完成增强操作,与装饰者类似。
- 使用前提:必须有接口
- 难点:需要反射技术
- 字节码增强,运行时创建目标类子类,从而进行增强
- 常见第三方框架:cglib、javassist等
(2)装饰者设计模式
设计模式:专门为解决某一类问题,而编写的固定格式的代码。
装饰者固定结构:接口A,已知实现类C,需要装饰者创建代理类B
- 创建类B,并实现接口A
- 提供类B的构造方法,参数类型A,用于接收A接口的其他实现类(C)
- 给类B添加类型为A成员变量,用于存放A接口的其他实现类
- 增强需要的方法
- 实现不需要增强的方法,方法里调用成员变量存放的其他实现类对应的方法
MyConnection.java
package cn.itheima.jdbc.DataSource; import java.sql.Array; import java.sql.Blob; import java.sql.CallableStatement; import java.sql.Clob; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.NClob; import java.sql.PreparedStatement; import java.sql.SQLClientInfoException; import java.sql.SQLException; import java.sql.SQLWarning; import java.sql.SQLXML; import java.sql.Savepoint; import java.sql.Statement; import java.sql.Struct; import java.util.LinkedList; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executor; //1.实现同一个接口Connection public class MyConnection implements Connection { //3.定义一个变量 private Connection conn; private LinkedList<Connection> pool; // 2.编写一个构造方法(参数使用了面相对象的多态特性) public MyConnection(Connection conn,LinkedList<Connection> pool) { this.conn=conn; this.pool=pool; } //4.书写需要增强的方法 @Override public void close() throws SQLException { pool.add(conn); } /** * 此方法必须覆盖!否则会出现空指针异常!!! */ @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } @Override public <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public Statement createStatement() throws SQLException { // TODO Auto-generated method stub return null; } @Override public CallableStatement prepareCall(String sql) throws SQLException { // TODO Auto-generated method stub return null; } @Override public String nativeSQL(String sql) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setAutoCommit(boolean autoCommit) throws SQLException { // TODO Auto-generated method stub } @Override public boolean getAutoCommit() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void commit() throws SQLException { // TODO Auto-generated method stub } @Override public void rollback() throws SQLException { // TODO Auto-generated method stub } @Override public boolean isClosed() throws SQLException { // TODO Auto-generated method stub return false; } @Override public DatabaseMetaData getMetaData() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setReadOnly(boolean readOnly) throws SQLException { // TODO Auto-generated method stub } @Override public boolean isReadOnly() throws SQLException { // TODO Auto-generated method stub return false; } @Override public void setCatalog(String catalog) throws SQLException { // TODO Auto-generated method stub } @Override public String getCatalog() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setTransactionIsolation(int level) throws SQLException { // TODO Auto-generated method stub } @Override public int getTransactionIsolation() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public SQLWarning getWarnings() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void clearWarnings() throws SQLException { // TODO Auto-generated method stub } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { // TODO Auto-generated method stub return null; } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Map<String, Class<?>> getTypeMap() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setTypeMap(Map<String, Class<?>> map) throws SQLException { // TODO Auto-generated method stub } @Override public void setHoldability(int holdability) throws SQLException { // TODO Auto-generated method stub } @Override public int getHoldability() throws SQLException { // TODO Auto-generated method stub return 0; } @Override public Savepoint setSavepoint() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Savepoint setSavepoint(String name) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void rollback(Savepoint savepoint) throws SQLException { // TODO Auto-generated method stub } @Override public void releaseSavepoint(Savepoint savepoint) throws SQLException { // TODO Auto-generated method stub } @Override public Statement createStatement(int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { // TODO Auto-generated method stub return null; } @Override public CallableStatement prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, int[] columnIndexes) throws SQLException { // TODO Auto-generated method stub return null; } @Override public PreparedStatement prepareStatement(String sql, String[] columnNames) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Clob createClob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Blob createBlob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public NClob createNClob() throws SQLException { // TODO Auto-generated method stub return null; } @Override public SQLXML createSQLXML() throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isValid(int timeout) throws SQLException { // TODO Auto-generated method stub return false; } @Override public void setClientInfo(String name, String value) throws SQLClientInfoException { // TODO Auto-generated method stub } @Override public void setClientInfo(Properties properties) throws SQLClientInfoException { // TODO Auto-generated method stub } @Override public String getClientInfo(String name) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Properties getClientInfo() throws SQLException { // TODO Auto-generated method stub return null; } @Override public Array createArrayOf(String typeName, Object[] elements) throws SQLException { // TODO Auto-generated method stub return null; } @Override public Struct createStruct(String typeName, Object[] attributes) throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setSchema(String schema) throws SQLException { // TODO Auto-generated method stub } @Override public String getSchema() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void abort(Executor executor) throws SQLException { // TODO Auto-generated method stub } @Override public void setNetworkTimeout(Executor executor, int milliseconds) throws SQLException { // TODO Auto-generated method stub } @Override public int getNetworkTimeout() throws SQLException { // TODO Auto-generated method stub return 0; } }
MyDataSource1.java
package cn.itheima.jdbc.DataSource; import java.io.PrintWriter; import java.sql.Connection; import java.sql.SQLException; import java.sql.SQLFeatureNotSupportedException; import java.util.LinkedList; import java.util.logging.Logger; import javax.sql.DataSource; import cn.itheima.jdbc.utils.JDBCUtils_V3; public class MyDataSource1 implements DataSource{ //1.创建1个容器用于存储Connection对象 private static LinkedList<Connection> pool = new LinkedList<Connection>(); //2.创建5个连接放到容器中去 static{ for (int i = 0; i < 5; i++) { Connection conn = JDBCUtils_V3.getConnection(); //放入池子中connection对象已经经过改造了 MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } /** * 重写获取连接的方法 */ @Override public Connection getConnection() throws SQLException { Connection conn = null; //3.使用前先判断 if(pool.size()==0){ //4.池子里面没有,我们再创建一些 for (int i = 0; i < 5; i++) { conn = JDBCUtils_V3.getConnection(); //放入池子中connection对象已经经过改造了 MyConnection myconn = new MyConnection(conn, pool); pool.add(myconn); } } //5.从池子里面获取一个连接对象Connection conn = pool.remove(0); return conn; } /** * 归还连接对象到连接池中去 */ public void backConnection(Connection conn){ pool.add(conn); } @Override public PrintWriter getLogWriter() throws SQLException { // TODO Auto-generated method stub return null; } @Override public void setLogWriter(PrintWriter out) throws SQLException { // TODO Auto-generated method stub } @Override public void setLoginTimeout(int seconds) throws SQLException { // TODO Auto-generated method stub } @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 <T> T unwrap(Class<T> iface) throws SQLException { // TODO Auto-generated method stub return null; } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { // TODO Auto-generated method stub return false; } @Override public Connection getConnection(String username, String password) throws SQLException { // TODO Auto-generated method stub return null; } }
TestMyDataSource.java
package cn.itheima.jdbc.test; import java.sql.Connection; import java.sql.PreparedStatement; import javax.sql.DataSource; import org.junit.Test; import cn.itheima.jdbc.DataSource.MyDataSource; import cn.itheima.jdbc.DataSource.MyDataSource1; import cn.itheima.jdbc.utils.JDBCUtils_V3; public class TestMyDataSource { /** * 添加用户 * 使用未改造过的connection */ @Test public void testAddUser() { Connection conn = null; PreparedStatement pstmt = null; // 1.创建自定义连接池对象 MyDataSource dataSource = new MyDataSource(); try { // 2.从池子中获取连接 conn = dataSource.getConnection(); String sql = "insert into user values(null,?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setString(1, "吕布"); pstmt.setString(2, "貂蝉"); int rows = pstmt.executeUpdate(); if (rows > 0) { System.out.println("添加成功!"); } else { System.out.println("添加失败!"); } } catch (Exception e) { throw new RuntimeException(e); } finally { dataSource.backConnection(conn); } } /** * 添加用户 * 使用改造过的connection */ @Test public void testAddUser1() { Connection conn = null; PreparedStatement pstmt = null; // 1.创建自定义连接池对象 DataSource dataSource = new MyDataSource1(); try { // 2.从池子中获取连接 conn = dataSource.getConnection(); String sql = "insert into user values(null,?,?)"; //3.必须在自定义的connection类中重写prepareStatement(sql)方法 pstmt = conn.prepareStatement(sql); pstmt.setString(1, "吕布1"); pstmt.setString(2, "貂蝉1"); int rows = pstmt.executeUpdate(); if (rows > 0) { System.out.println("添加成功!"); } else { System.out.println("添加失败!"); } } catch (Exception e) { throw new RuntimeException(e); } finally { JDBCUtils_V3.release(conn, pstmt, null); } } }
JDBCUtils_V3.java
package cn.itheima.jdbc.utils; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import java.util.ResourceBundle; /** * 提供获取连接和释放资源的 方法 * * @author Never Say Never * @date 2017年10月29日 * @version V1.0 */ public class JDBCUtils_V3 { private static String driver; private static String url; private static String username; private static String password; /** * 静态代码块加载配置文件信息 */ static { try { // 1.通过当前类获取类加载器 ClassLoader classLoader = JDBCUtils_V3.class.getClassLoader(); // 2.通过类加载器的方法获得一个输入流 InputStream is = classLoader.getResourceAsStream("db.properties"); // 3.创建一个properties对象 Properties props = new Properties(); // 4.加载输入流 props.load(is); // 5.获取相关参数的值 driver = props.getProperty("driver"); url = props.getProperty("url"); username = props.getProperty("username"); password = props.getProperty("password"); } catch (IOException e) { e.printStackTrace(); } } /** * 获取连接方法 * * @return */ public static Connection getConnection() { Connection conn = null; try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); } catch (Exception e) { e.printStackTrace(); } return conn; } /** * 释放资源方法 * * @param conn * @param pstmt * @param rs */ public static void release(Connection conn, PreparedStatement pstmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pstmt != null) { try { pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
4. C3P0连接池
C3P0开源免费的连接池。目前使用它的开源项目有:Spring、Hibernate等。使用第三方工具需要导入jar包,c3p0使用时可以添加配置文件c3p0-config.xml(或者c3p0.properties,大部分使用c3p0-config.xml),也可以不添加配置文件。它有两种使用方式,但是多数情况下都是用需要配置文件的那种方式。
(1)导入jar包
如果使用的是0.9.2版本,需要导入两个jar包。
如果使用的是0.9.1版本,只需要导入c3p0-0.9.1.2.jar。
(2)配置文件
- 配置文件名称:c3p0-config.xml(固定)
- 配置文件位置:src(类路径)
- 配置文件内容:命名配置
<c3p0-config> <named-config name="itheima"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///web08</property> <property name="user">root</property> <property name="password">123456</property> </named-config> </c3p0-config>
- 配置文件内容:默认配置
<c3p0-config> <default-config> <property name="automaticTestTable">con_test</property> <property name="checkoutTimeout">30000</property> <property name="idleConnectionTestPeriod">30</property> <property name="initialPoolSize">10</property> <property name="maxIdleTime">30</property> <property name="maxPoolSize">100</property> <property name="minPoolSize">10</property> <property name="maxStatements">200</property> </default-config> </c3p0-config>
(3)示例
c3p0提供核心工具类:ComboPooledDataSource,如果要使用连接池,必须创建该类的实例对象。
- new ComboPooledDataSource("itcast"); 使用配置文件“命名配置”
- <name-config name="itcast">
- new ComboPooledDataSource(); 使用配置文件“默认配置”
- <default-config>
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///web08</property> <property name="user">root</property> <property name="password">123456</property> <property name="initialPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> <named-config name="itheima"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql:///web08</property> <property name="user">root</property> <property name="password">123456</property> </named-config> </c3p0-config>
import java.sql.Connection; import java.sql.PreparedStatement; import org.junit.Test; import com.mchange.v2.c3p0.ComboPooledDataSource; import cn.itheima.jdbc.utils.JDBCUtils_V3; public class TestC3P0 { @Test public void testAddUser() { Connection conn = null; PreparedStatement pstmt = null; // 1.创建自定义连接池对象 ComboPooledDataSource dataSource = new ComboPooledDataSource();// 加载默认的配置 // ComboPooledDataSource dataSource = new ComboPooledDataSource("itheima");//加载有名称的配置 try { // 2.从池子中获取连接 conn = dataSource.getConnection(); String sql = "insert into tbl_user values(null,?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setString(1, "吕布2"); pstmt.setString(2, "貂蝉2"); int rows = pstmt.executeUpdate(); if (rows > 0) { System.out.println("添加成功!"); } else { System.out.println("添加失败!"); } } catch (Exception e) { throw new RuntimeException(e); } finally { JDBCUtils_V3.release(conn, pstmt, null); } } }
import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Properties; import java.util.ResourceBundle; /** * 提供获取连接和释放资源的 方法 * * @author Never Say Never * @date 2017年10月29日 * @version V1.0 */ public class JDBCUtils_V3 { private static String driver; private static String url; private static String username; private static String password; /** * 静态代码块加载配置文件信息 */ static { try { // 1.通过当前类获取类加载器 ClassLoader classLoader = JDBCUtils_V3.class.getClassLoader(); // 2.通过类加载器的方法获得一个输入流 InputStream is = classLoader.getResourceAsStream("db.properties"); // 3.创建一个properties对象 Properties props = new Properties(); // 4.加载输入流 props.load(is); // 5.获取相关参数的值 driver = props.getProperty("driver"); url = props.getProperty("url"); username = props.getProperty("username"); password = props.getProperty("password"); } catch (IOException e) { e.printStackTrace(); } } /** * 获取连接方法 * * @return */ public static Connection getConnection() { Connection conn = null; try { Class.forName(driver); conn = DriverManager.getConnection(url, username, password); } catch (Exception e) { e.printStackTrace(); } return conn; } /** * 释放资源方法 * * @param conn * @param pstmt * @param rs */ public static void release(Connection conn, PreparedStatement pstmt, ResultSet rs) { if (rs != null) { try { rs.close(); } catch (SQLException e) { e.printStackTrace(); } } if (pstmt != null) { try { pstmt.close(); } catch (SQLException e) { e.printStackTrace(); } } if (conn != null) { try { conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } }
可以写一个工具类,修改下相关代码:
import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class C3P0Utils { private static ComboPooledDataSource dataSource = new ComboPooledDataSource("itheima"); public static DataSource getDataSource() { return dataSource; } public static Connection getConnection() { try { return dataSource.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } }
import java.sql.Connection; import java.sql.PreparedStatement; import org.junit.Test; import com.mchange.v2.c3p0.ComboPooledDataSource; import cn.itheima.jdbc.utils.C3P0Utils; import cn.itheima.jdbc.utils.JDBCUtils_V3; public class TestC3P0 { @Test public void testAddUser1() { Connection conn = null; PreparedStatement pstmt = null; try { // 2.从池子中获取连接 conn = C3P0Utils.getConnection(); String sql = "insert into tbl_user values(null,?,?)"; pstmt = conn.prepareStatement(sql); pstmt.setString(1, "吕布3"); pstmt.setString(2, "貂蝉3"); int rows = pstmt.executeUpdate(); if (rows > 0) { System.out.println("添加成功!"); } else { System.out.println("添加失败!"); } } catch (Exception e) { throw new RuntimeException(e); } finally { JDBCUtils_V3.release(conn, pstmt, null); } } }
5. DBCP连接池
DBCP也是一个开源的连接池,是Apache上的一个java连接池项目,在企业开发中也比较常见,tomcat内置的连接池。
(1)导入jar包
commons-dbcp-1.4.jar和commons-pool-1.5.6.jar
(2)配置文件
- 配置文件名称:*.properties
- 配置文件位置:任意,建议src(classpath/类路径)
- 配置文件内容:properties不能写中文,不支持在STS(Eclipse的一个变种)中修改,必须使用记事本修改内容,否则中文注释就乱码了
driver=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/web08?useUnicode=true&characterEncoding=utf8 username=root password=123456
import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSourceFactory; public class DBCPUtils { private static DataSource dataSource; static{ try { //1.加载找properties文件输入流 InputStream is = DBCPUtils.class.getClassLoader().getResourceAsStream("db.properties"); //2.加载输入流 Properties props = new Properties(); props.load(is); //3.创建数据源 dataSource = BasicDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new RuntimeException(e); } } public static DataSource getDataSource(){ return dataSource; } public static Connection getConnection(){ try { return dataSource.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } }
import java.sql.Connection; import java.sql.PreparedStatement; import org.junit.Test; import cn.itheima.jdbc.utils.DBCPUtils; public class TestDBCP { @Test public void testUpdateUserById(){ Connection conn = null; PreparedStatement pstmt = null; try { conn = DBCPUtils.getConnection(); String sql ="update tbl_user set upassword=? where uid=?"; pstmt= conn.prepareStatement(sql); pstmt.setString(1, "柳岩"); pstmt.setInt(2, 20); int rows = pstmt.executeUpdate(); if(rows>0){ System.out.println("更新成功!"); }else{ System.out.println("更新失败!"); } } catch (Exception e) { throw new RuntimeException(e); } } }
二、DBUtils
如果在某些项目中,不使用框架,只使用JDBC开发,我们会发现冗余代码过多:
为了简化JDBC开发,我们采用apache commons组件的一个成员:DBUtils。
DBUtils就是JDBC的简化开发工具包。需要使用的技术:连接池(只用到里面的获得连接),SQL语句并没有变少以及设置sql语句中的参数。
1. JavaBean组件
JavaBean就是一个类,在开发中常用语封装数据。具有如下特性:
- 需要实现接口:java.io.Serializable,通常偷懒省略了。
- 提供私有字段:private 类型 字段名;
- 提供getter/setter方法
- 提供无参构造
一般将JavaBean类放到domain包或model包里。
2. DBUtils介绍
(1)概述
DBUtils封装了对JDBC的操作,简化了JDBC操作,可以少写代码。在不使用框架的情况下,使用DBUtils的概率非常大。
DBUtils三个核心功能介绍:
- QueryRunner类,提供对sql语句操作的API
- ResultSetHandler接口,用于定义select操作后,怎样封装结果集
- DbUtils类,它就是一个工具类,定义了关闭资源与事务处理的方法
(2)QueryRunner核心类
- QueryRunner(Database ds),提供了数据源(连接池),DBUtils底层自动维护connection
- update(String sql, Object... params),执行更新数据。(增、删、改)
- query(String sql, ResultSetHandler<T> rsh, Object... params),执行查询
(3)ResultSetHandler结果处理集类
我们知道在执行select语句之后得到的是ResultSet,然后我们还需要对ResultSet进行转换,得到最终我们想要的数据。你可能希望把ResultSet的数据放到一个List中,也可能想把数据放到一个Map中,或是一个Bean中。
DBUtils提供了一个接口ResultSetHandler,它就是用来ResultSet转换成目标类型的工具。你可以自己去实现这个接口,把ResultSet转换成你想要的类型。
DBUtils提供了很多个ResultSetHandler接口的实现,这些实现已经基本够用了,我们通常不用自己去实现ResultSet接口了。
- MapHandler:单行处理器!把结果集转换成Map<String,Object>,其中列名为键
- MapListHandler:多行处理器!把结果集转换成List<Map<String,Object>>;
- BeanHandler:单行处理器!把结果集转换成Bean,该处理器需要Class参数,即Bean的类型;
- BeanListHandler:多行处理器!把结果集转换成List<Bean>;
- ColumnListHandler:多行单列处理器!把结果集转换成List<Object>,使用ColumnListHandler时需要指定某一列的名称或编号,例如:new ColumListHandler(“name”)表示把name列的数据放到List中。
- ScalarHandler:单行单列处理器!把结果集转换成Object。一般用于聚集查询,例如select count(*) from tab_student。
ArrayHandler | 将结果集中的第一条记录封装到一个Object[]数组中,数组中的每一个元素就是这条记录中的每一个字段的值 |
ArrayListHangler | 将结果集中的第一条记录封装到一个Object[]数组中,将这些数组在封装到List集合中 |
BeanHandler | 将结果集中的每一条记录封装到一个指定的javaBean中 |
BeanListHandler | 将结果集中每一条记录封装到指定的javaBean中,将这些JavaBean在封装到List集合中 |
ColumnListHandler | 将结果集中指定的列的字段值,封装到一个List集合中 |
KeyedHandler | 将结果集中每一条记录封装到Map<String,Object>,在将这个map结婚作为另一个Map的value,另一个Map集合的key是指定的字段的名称 |
MapHandler | 将结果集中第一条记录封装到Map<String,Object>集合中,key就是字段名称,value就是字段值 |
MapListHandler | 将结果集中每一条记录封装到了Map<String,Object>集合中,key就是字段名称,value就是字段值,在将这些Map封装到List集合中 |
ScalarHandler | 它用于单数据。例如select count(*) from table 操作 |
Map处理器:
每一条记录作为一个Map,Map的key是字段名称。
Bean处理器 :
必须搞一个javaBean。
Column处理器:
Scalar处理器:
(4)DbUtils工具类
- closeQuietly(Connection conn) 关闭连接,如果有异常try后不抛
- commitAndCloseQuietly(Connection conn) 提交并关闭连接
- rollbackAndCloseQuietly(Connection conn) 回滚并关闭连接
3. 示例:使用DBUtils完成增删改查操作
public class User { private int uid; private String uname; private String upassword; public User() { } public int getUid() { return uid; } public void setUid(int uid) { this.uid = uid; } public String getUname() { return uname; } public void setUname(String uname) { this.uname = uname; } public String getUpassword() { return upassword; } public void setUpassword(String upassword) { this.upassword = upassword; } }
import java.sql.Connection; import java.sql.SQLException; import javax.sql.DataSource; import com.mchange.v2.c3p0.ComboPooledDataSource; public class C3P0Utils { private static ComboPooledDataSource dataSource = new ComboPooledDataSource("itheima"); public static DataSource getDataSource() { return dataSource; } public static Connection getConnection() { try { return dataSource.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } }
import java.sql.SQLException; import org.apache.commons.dbutils.QueryRunner; import org.junit.Test; import cn.itheima.jdbc.utils.C3P0Utils; /** * 测试DBUtils工具类的增删改操作 * * @author Never Say Never * @date 2017年10月31日 * @version V1.0 */ public class TestDBUtils1 { /** * 添加所有用户方法 */ @Test public void testAddUser() { try { // 1.创建核心类QueryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写SQL语句 String sql = "insert into user values(null,?,?)"; // 3.为占位符设置值 Object[] params = { "余淮", "耿耿" }; // 4.执行添加操作 int rows = qr.update(sql, params); if (rows > 0) { System.out.println("添加成功!"); } else { System.out.println("添加失败!"); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 根据id修改用户方法 * */ @Test public void testUpdateUserById() { try { // 1.创建核心类QueryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写SQL语句 String sql = "update user set upassword=? where uid=?"; // 3.为站位符设置值 Object[] params = { "xxx", 21 }; // 4.执行添加操作 int rows = qr.update(sql, params); if (rows > 0) { System.out.println("修改成功!"); } else { System.out.println("修改失败!"); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * 根据id删除用户方法 */ @Test public void testDeleteUserById() { try { // 1.创建核心类QueryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写SQL语句 String sql = "delete from user where uid=?"; // 3.为站位符设置值 Object[] params = {19}; // 4.执行添加操作 int rows = qr.update(sql, params); if (rows > 0) { System.out.println("删除成功!"); } else { System.out.println("删除失败!"); } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
import java.sql.SQLException; import java.util.List; import java.util.Map; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.handlers.BeanHandler; import org.apache.commons.dbutils.handlers.BeanListHandler; import org.apache.commons.dbutils.handlers.ColumnListHandler; import org.apache.commons.dbutils.handlers.MapListHandler; import org.apache.commons.dbutils.handlers.ScalarHandler; import org.junit.Test; import cn.itheima.domain.User; import cn.itheima.jdbc.utils.C3P0Utils; /** * 测试DBUtils查询操作 * * @author Never Say Never * @date 2017年10月31日 * @version V1.0 */ public class TestDBUtils2 { /* * 查询所有用户方法 */ @Test public void testQueryAll() { try { // 1.获取核心类queryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写sql语句 String sql = "select * from user"; // 3.执行查询操作 List<User> users = qr.query(sql, new BeanListHandler<User>(User.class)); // 4.对结果集集合进行遍历 for (User user : users) { System.out.println(user.getUname() + " : " + user.getUpassword()); } } catch (SQLException e) { throw new RuntimeException(e); } } /* * 根据id查询用户方法 */ @Test public void testQueryUserById() { try { // 1.获取核心类queryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写sql语句 String sql = "select * from user where uid=?"; //3.为占位符设置值 Object[] params = {21}; // 4.执行查询操作 User user = qr.query(sql, new BeanHandler<User>(User.class), params); System.out.println(user.getUname() + " : " + user.getUpassword()); } catch (SQLException e) { throw new RuntimeException(e); } } /* * 根据所有用户的总个数 */ @Test public void testQueryCount() { try { // 1.获取核心类queryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写sql语句 String sql = "select count(*) from tbl_user"; // 4.执行查询操作 Long count = (Long) qr.query(sql, new ScalarHandler()); System.out.println(count); } catch (SQLException e) { throw new RuntimeException(e); } } /* * 查询所有用户方法 */ @Test public void testQueryAll1() { try { // 1.获取核心类queryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写sql语句 String sql = "select * from user"; // 3.执行查询操作 List<Map<String, Object>> list = qr.query(sql, new MapListHandler()); // 4.对结果集集合进行遍历 for (Map<String, Object> map : list) { System.out.println(map); } } catch (SQLException e) { throw new RuntimeException(e); } } /* * 查询所有用户方法 */ @Test public void testQueryAll2() { try { // 1.获取核心类queryRunner QueryRunner qr = new QueryRunner(C3P0Utils.getDataSource()); // 2.编写sql语句 String sql = "select * from user"; // 3.执行查询操作 List<Object> list = qr.query(sql, new ColumnListHandler("uname")); // 4.对结果集集合进行遍历 for (Object object : list) { System.out.println(object); } } catch (SQLException e) { throw new RuntimeException(e); } } }
【注意】:DBUtils在创建QueryRunner时传入dataSource对象,每次在执行完之后都会自动关闭Connection连接对象。(就相当于把conn交给dbutils管理了,他会帮我们关掉)
如果没有传入dataSource的话,需要手动关闭。