web 学习笔记16-JDBC连接池 扩展已知类
2017-07-25 00:09
441 查看
1、连接池:
2、我们实际开发中一般使用数据源 DataSource:
3、扩展已知类功能-包装类:
4、扩展已知类功能-静态代理
5、扩展已知类功能-动态代理
6、使用动态代理改造我们的 MyDataDource2 类:
7、DBCP数据源:
8、C3P0数据源:
9、tomcat数据源的配置
10、数据库元数据的获取:
11、自定义JDBC的框架
为什么要用连接池,如果你的服务器servlet有个添加数据的方法,里面需要获取一个数据库连接,如果有100000个人同时访问 那么你就需要创建10000个连接,而且连接是个很耗时的操作,也会浪费内存,导致服务器内存溢出。 模拟下连接池,就类似一个池子,提前放好一些连接,需要的时候直接拿来用。 例如:mysql的jar包需要先导入。 a.一个数据库的工具类
public class JdbcUtils { private static String driverClass = "" ; private static String url = "" ; private static String user = "" ; private static String password = ""; static{ ResourceBundle rb = ResourceBundle.getBundle("dbcfg") ; driverClass = rb.getString("driverClass") ; url = rb.getString("url") ; user = rb.getString("user") ; password = rb.getString("password") ; try { Class.forName(driverClass) ; } catch (ClassNotFoundException e) { e.printStackTrace(); } } public static Connection getConnection(){ try { return DriverManager.getConnection(url, user, password) ; } catch (SQLException e) { e.printStackTrace(); } return null ; } public static void release(ResultSet rs ,Statement stmt,Connection conn){ if(rs != null){ try { rs.close() ; } catch (SQLException e) { e.printStackTrace(); } } if(stmt != null){ try { stmt.close() ; } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } if(conn != null){ try { conn.close() ; } catch (SQLException e) { e.printStackTrace(); } } } }
b.模拟创建一个连接池
public class MyConnectionPool1 { //增删方便 private static LinkedList<Connection> pool = new LinkedList<Connection>() ; static{ for (int i = 0; i < 10; i++) { Connection conn = JdbcUtils.getConnection() ; pool.add(conn) ; } } public synchronized static Connection getConnection(){ if(pool.size() > 0) return pool.removeFirst() ; else throw new RuntimeException("对不起,服务器忙") ; } public static void close(Connection conn){ if(conn != null) pool.addLast(conn) ; //放回池中 } }
c.模拟连接池的原理(dao层的实现类)
public class DaoImpl { @Test public void add(){ //拿到连接 Connection conn = null ; PreparedStatement pstmt = null ; try { conn = MyConnectionPool1.getConnection() ; // 左边; 抽象层 右边: 真实的对象 com.mysql.jdbc.Connecotion pstmt = conn.prepareStatement("") ; //....... } catch (Exception e) { e.printStackTrace() ; }finally{ JdbcUtils.release(null, pstmt, null) ; MyConnectionPool1.close(conn) ; } } }
2、我们实际开发中一般使用数据源 DataSource:
底层也是连接池,我们创建一个类,实现DataSource接口 例如
public class MyDataDource2 implements DataSource{ private static LinkedList<Connection> pool = new LinkedList<Connection>() ; static{//写一个静态块,默认放10个连接 for (int i = 0; i < 10; i++) { Connection conn = JdbcUtils.getConnection() ; pool.add(conn) ; } } @Override public Connection getConnection() throws SQLException { if(pool.size() > 0 ){ return pool.removeFirst() ; //右边: com.mysql.jdbc.Connection }else throw new RuntimeException("对不起,服务器忙") ; } //未实现的一些方法 }
我们的dao实现类:
public class DaoImpl { private DataSource ds ; public DaoImpl(DataSource ds) {//我们将数据源通过构造函数传进来 this.ds = ds ; } @Test public void add(){ DataSource ds = new MyDataDource2() ; ; //拿到连接 Connection conn = null ; PreparedStatement pstmt = null ; try { conn = ds.getConnection() ; //通过数据源去拿取连接 pstmt = conn.prepareStatement("") ; //....... } catch (Exception e) { e.printStackTrace() ; }finally{ if(pstmt != null){ try { pstmt.close() ; } catch (SQLException e) { e.printStackTrace(); } } if(conn != null){ try { conn.close() ; //不能关,这个是调用真实mysql连接对象的close方法 //一定要还回池中,此时需要改写close方法. } catch (SQLException e) { e.printStackTrace(); } } } } }
我们现在遇到的一个问题就是:我们拿到了conn,但是不能关(关了连接池的意义就没了),又不能放回池中。 我的人想在MyDataDource2类中添加一个close方法,然后调用,实际是不行的(conn是一个接口,怎么可能调用它实现类的方法呢)
3、扩展已知类功能-包装类:
通常3种方法: 子类 包装类(装饰模式、适配器模式) 动态代理 上面有个不能调用close方法关闭连接,有放回不到池中,我们就想办法扩展mysql的Connect类的功能 然close方法不是关闭连接,而是将连接放回池中。 如何扩展已知类的功能(不能修改源码) a.子类 继承父类 不好,不用,与具体的类相关 b.包装类(装饰模式) 步骤 1. 写一个类,实现和被包装类相同的接口 (使他们具有相同的行为) 2. 创建一个实例变量,引用被包装类对象 (最好做到与具体类无关) 3. 编写一个构造函数,传入被包装类对象 (注入: DI) 4. 对于需要改写的方法,写自己的代码 5. 对于不需要改写的方法,引用被包装类的对象的对应方法即可 例如:
创建个包装类 public class MyConnection3 implements Connection{//步骤1 private Connection conn ;//步骤2,这个是java.sql接口,mysql里面的Connect也实现了这个接口 private LinkedList<Connection> pool ; //注意,这个形参的conn就是你传进来的mysql的真实对象。如果你传的oracle的conn,就包装oracle public MyConnection3(Connection conn,LinkedList<Connection> pool){//步骤3 this.conn = conn ; this.pool = pool ; } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql);//步骤5,默认使用mysql的prepareStatement方法 } @Override public void close() throws SQLException {//步骤4 //需要将连接还回池中 pool.addLast(conn) ; } //------------下面是默认实现的接口,省略, }
改写下数据源类
public class MyDataDource2 implements DataSource{ private static LinkedList<Connection> pool = new LinkedList<Connection>() ; static{//写一个静态块,默认放10个连接 for (int i = 0; i < 10; i++) { Connection conn = JdbcUtils.getConnection() ; pool.add(conn) ; } } @Override public Connection getConnection() throws SQLException { if(pool.size() > 0 ){ Connection conn = pool.removeFirst() ; //右边: com.mysql.jdbc.Connection MyConnection3 mconn = new MyConnection3(conn,pool);//这里我们使用了自己改写的包装类,可以调用自己的close return mconn; }else throw new RuntimeException("对不起,服务器忙") ; } //未实现的一些方法 }
我们的dao实现类不需要改,直接可以调用close方法了,就把conn放回到池子中了。 注意:关键地方就是我们创建了一个自己的类MyConnection3,替代了mysql里面的Connection类的功能。 我们定义的类可以自定义,功能也可以更多。 c.适配器模式: 和包装类似,只不过这个将适配器类单独写出来,我们再写一个类继承适配器类。 创建一个适配器类: 步骤: 1. 写一个类,实现和被包装类相同的接口 (使他们具有相同的行为) 2. 创建一个实例变量,引用被包装类对象 (最好做到与具体类无关) 3. 编写一个构造函数,传入被包装类对象 (注入: DI) 4. 对所有的方法方法,引用被包装类的对象的对应方法即可
public class MyConenctionAdapter implements Connection { private Connection conn ; public MyConenctionAdapter(Connection conn) { this.conn = conn ; } @Override public PreparedStatement prepareStatement(String sql) throws SQLException { return conn.prepareStatement(sql); } //----以下所有的方法都调用mysql connect类里面的方法 }
创建一个我们自己的类,继承这个适配器类,然后重写下close方法: 步骤: 1. 写一个类,继承适配器类 (使他们具有相同的行为) 2. 创建一个实例变量,引用被包装类对象 (最好做到与具体类无关) 3. 编写一个构造函数,传入被包装类对象 (注入: DI) 4. 对需要改写的方法重写代码即可
public class MyConnection extends MyConenctionAdapter { private Connection conn ; private LinkedList<Connection> pool ; public MyConnection(Connection conn,LinkedList<Connection> pool) { super(conn) ; this.pool = pool ; this.conn = conn; } @Override public void close() throws SQLException { pool.add(conn) ;//重写close方法,将连接放回池中 } }
上面2种方式类似,都是通过实现了你要扩展类的相同接口。 包装类是直接实现接口的时候就把close方法重写了 适配器模式就是你继承的时候再重写close方法。
4、扩展已知类功能-静态代理
实际中不用 简单的代码演示下: a.有个Person类:
public class Person { public void eat() { System.out.println("吃饭"); } public void sleep(){ System.out.println("睡觉"); } }
b.创建一个代理类:
//我们代理类里面定义和Person类里面一样的方法 public class ProxyPerson { private Person p ; public ProxyPerson(Person p) {//将person对象传进来 this.p = p ; } public void eat() { p.eat() ; } public void sleep(){ p.sleep() ; System.out.println("睡5分钟"); } }
c.使用:
public class Test { public static void main(String[] args) { //没有使用代理 Person p = new Person(); p.eat() ; p.sleep() ; //使用代理, ProxyPerson pp = new ProxyPerson(p) ; pp.eat() ; pp.sleep() ;//除了打印“睡觉” 还会打印“睡5分钟” } }
注意:和包装类相似,只不过我们写了和被包装类一样的方法,而不是实现接口。
5、扩展已知类功能-动态代理
简单代码演示下动态代理的原理 a.有个通用的接口
public interface Human { public void eat() ; public void sing(float money) ; public void dance(float money) ; }
b.一个实现类(可以理解 歌手)
public class SpringBrother implements Human { @Override public void eat() { System.out.println("吃饭"); } @Override public void sing(float money) { System.out.println("拿到" + money); } @Override public void dance(float money) { System.out.println("拿到" + money); } }
c.代理类(可理解为经纪人)
//模拟动态代理的原理 public class ProxyHuman implements Human { private Human man ; public ProxyHuman(Human man) { this.man = man ; } @Override public void eat() { man.eat() ; } @Override public void sing(float money) { if(money > 1000) man.sing(money/2) ; } @Override public void dance(float money) { if(money > 2000) man.sing(money/2) ; } }
d.使用代理类
public class Test { public static void main(String[] args) { Human man = new ProxyHuman(new SpringBrother()) ; man.eat() ; man.sing(2000) ; man.dance(4000) ; } }
实际的应用中是没有ProxyHuman这个代理类的,java虚拟机帮我们做了,我们需要使用Proxy类 代码演示:基于接口的动态代理
public class Test { public static void main(String[] args) { //定义为final是为了下面匿名内部类可以调用 final SpringBrother sb = new SpringBrother() ; //可以查看帮助文档,看看参数 Human man = (Human)Proxy.newProxyInstance(sb.getClass().getClassLoader(), sb.getClass().getInterfaces(), new InvocationHandler() { /** * invoke(Object proxy, Method method, Object[] args) * proxy: 代理人 * method: 代理的方法 * args: 方法的参数 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(method.getName().equals("sing")){//唱歌方法 float money = (Float)args[0] ; if(money > 1000){ Object retVal = method.invoke(sb, money/2) ; return retVal; }else return null ; } if(method.getName().equals("dance")){//跳舞方法 float money = (Float)args[0] ; if(money > 2000){ Object retVal = method.invoke(sb, money/2) ; return retVal ; }else return null ; } Object ret =method.invoke(sb, args) ; return ret ; } }) ; man.eat() ; man.sing(1500) ; man.dance(2500) ; } }
其实也很简单,我们是用了java提供了类来实现了动态代理的功能 基于子类的动态代理可以使用第三方包net.sf.cglib.proxy.Enhancer来实现,就不演示了。 注意:我们动态代理就是给某个特定的方法设个条件是否执行,或者在调用这个close方法的时候不执行,来执行我们自己的代码。 就相当于包装类里面,我们重新实现了接口里面的close方法。
6、使用动态代理改造我们的 MyDataDource2 类:
例如:
public class MyDataDource2 implements DataSource{ private static LinkedList<Connection> pool = new LinkedList<Connection>() ; static{//写一个静态块,默认放10个连接 for (int i = 0; i < 10; i++) { Connection conn = JdbcUtils.getConnection() ; pool.add(conn) ; } } @Override public Connection getConnection() throws SQLException { if(pool.size() > 0 ){ //定义为final,便于匿名内部类使用 final Connection conn = pool.removeFirst() ; //右边: com.mysql.jdbc.Connection //采用动态代理 Connection ProxyConn = (Connection)Proxy.newProxyInstance(conn.getClass().getClassLoader(), conn.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object arg0, Method method, Object[] args) throws Throwable { if(method.getName().equals("close")){ //不要关闭,而是放回池中 pool.add(conn) ; return null ; } Object retVal = method.invoke(conn, args) ; return retVal ; } }) ; return ProxyConn; }else throw new RuntimeException("对不起,服务器忙") ; } //未实现的一些方法 }
7、DBCP数据源:
第三方提供的数据源 使用步骤: a.将jar包拷贝到当前工程 commons-dbcp-1.4.jar commons-pool-1.5.6.jar b.在src目录下创建一个配置文件 dfcpconfig.properties #连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test username=root password=root #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=50 #<!-- 最大空闲连接 --> maxIdle=20 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=gbk #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly=false #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=READ_UNCOMMITTED c.创建一个连接的工具类
public class DBCPUtils { private static DataSource ds ; static { //将配置文件加载进来 InputStream in = DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties") ; Properties props = new Properties() ; try { props.load(in) ; ds = BasicDataSourceFactory.createDataSource(props) ; } catch (Exception e) { throw new RuntimeException("服务器忙") ; } } //提供获取练级的方法 public static Connection getConnection(){ try { return ds.getConnection() ; } catch (SQLException e) { throw new RuntimeException("服务器忙") ; } } }
d.使用
public class TestDBCPUtils { public static void main(String[] args) { Connection conn = DBCPUtils.getConnection() ; System.out.println(conn.getClass().getName()); } }
8、C3P0数据源:
第三方数据源 和上面的方法类似 a.拷贝jar包 b.在src目录下创建配置文件 c3p0-config.xml
<?xml version="1.0" encoding="UTF-8"?> <c3p0-config> <default-config> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </default-config> <named-config name="mysql"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </named-config> <named-config name="oracle"> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/day16</property> <property name="user">root</property> <property name="password">root</property> <property name="acquireIncrement">5</property> <property name="initialPoolSize">10</property> <property name="minPoolSize">5</property> <property name="maxPoolSize">20</property> </named-config> </c3p0-config>
c.创建工具类
public class C3p0Utils { private static DataSource ds ; static{ ds = new ComboPooledDataSource() ; } //提供获取连接的方法 public static Connection getConnection(){ try { return ds.getConnection() ; } catch (SQLException e) { throw new RuntimeException("服务器忙") ; } } }
d.使用:
public class TestC3p0Utils { public static void main(String[] args) { Connection conn = C3p0Utils.getConnection() ; System.out.println(conn.getClass().getName()); } }
上面两种情况是在java工程里面使用第三方的数据源,我们web工程里面可以使用tomcat自带的数据源
9、tomcat数据源的配置
tomcat里面已经有了dbcp的jar包了 D:\tomcat\apache-tomcat-7.0.77\lib\tomcat-dbcp.jar 步骤: a、拷贝数据库驱动jar包到Tomcat\lib目录下 b、在应用的META-INF目录下建立一个名称为context.xml的配置文件。
可以查看帮助文档 <?xml version = "1.0"?> <Context> <Resource name="jdbc/day16" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="root" password="root" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test"/> </Context>
c、启动Tomcat,数据源就给你建好了 d、在应用中如何获取数据源,jsp页面中使用
<% Context initContext = new InitialContext(); Context envContext = (Context) initContext.lookup("java:/comp/env"); DataSource ds = (DataSource) envContext.lookup("jdbc/test"); Connection conn = ds.getConnection(); out.write(conn.toString() + "jjjjj") ; %>
注意:不要在main方法中获取数据源,获取不到。因为main调用重新开了个虚拟机。
10、数据库元数据的获取:
MetaData
元数据:数据库、表、列等定义的信息
例如:数据库的元信息:DatabaseMetaData
Connection conn = DBCPUtils.getConnection();// 获取连接对象 DatabaseMetaData dbmd = conn.getMetaData();// 获取DataBaseMetaData对象 String name = dbmd.getDatabaseProductName();// 获取数据库产品的名称
获取PreparedStatement占位符的元信息
Connection conn = DBCPUtils.getConnection();// 获取连接对象 PreparedStatement pstmt = conn.prepareStatement("??????");// 创建预处理对象 ParameterMetaData pmd = pstmt.getParameterMetaData();// 获取ParameterMetaData对象 int n = pmd.getParameterCount();// 获取参数的个数
等等,我们其实都可以通过方法的名称就知道。
11、自定义JDBC的框架
之前我们DaoImpl里面的数据库操作很麻烦,而且很多重复,我们可以封装下。 这个地方我们就用到元数据了,可以动态的获取参数个数,返回查询结果的字段名称等。 我们新建一个DBAsssist类,里面把一些连接,执行sql语句的逻辑放进来
//自定义框架 public class DBAsssist { // 执行添改删语句,定义可变参数好处就是,有参数就传,没有就不传 public boolean update(String sql, Object... params) { Connection conn = DBCPUtils.getConnection();// 拿到连接对象 int t = 0; try { // 创建预处理命令对象 PreparedStatement pstmt = conn.prepareStatement(sql); // 对?进行赋值 // 获取ParameterMetaData对象 ParameterMetaData pmd = pstmt.getParameterMetaData(); int n = pmd.getParameterCount();// 拿到?的个数 if (n > 0) { if (params == null || params.length != n) {// sql语句里有?号 throw new RuntimeException("参数的个数不匹配"); } for (int i = 0; i < n; i++) {// 依次给每个?赋值 pstmt.setObject(i + 1, params[i]); } } t = pstmt.executeUpdate();//执行sql语句 } catch (SQLException e) { e.printStackTrace(); } finally { try { conn.close(); // 还回池中了 } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); } } return t > 0 ? true : false; } //查询的就不演示了 }
使用:
@Test public void test() { DBAsssist db = new DBAsssist(); db.update("insert into account(id,name,money) values(?,?,?)", 1, "张三",2000); }
后面我们可以把我们写好的类打包成jar包,其他的工程就可以使用了。
相关文章推荐
- perl学习笔记16--Web自动化和连网
- JDBC五数据源和数据池(web基础学习笔记十一)
- 学习笔记_java web——数据源和连接池
- 【JAVAWEB学习笔记】10_JDBC连接池&DBUtils
- JDBC三(web基础学习笔记九)
- Asp.Net Ajax 学习笔记16 Profile Service扩展方式
- JDBC一(web基础学习笔记七)
- 构建高性能的web站点学习笔记二------数据库扩展
- Quartz学习笔记(五) quartz扩展druid连接池
- javaweb--jdbc--数据库操作学习笔记
- JDBC二查询(web基础学习笔记八)
- JDBC四(web基础学习笔记十)
- 学习笔记:jdbc连接、操作数据库SQL Server 2008 ——MyEclipse web示例
- 【数据库学习笔记】(4)JDBC数据源和连接池
- 构建高性能的web站点学习笔记二------数据库扩展
- Apache Shiro学习笔记(五)Web集成扩展
- web 学习笔记15-JDBC大数据 批处理 存储过程 事务
- web 学习笔记14-JDBC
- JDBC--学习笔记(三)数据源与连接池
- Android开发学习笔记:浅谈WebView