JavaWeb 后端 <十> 之 数据池 C3P0 DPCB JNDI
一、数据库连接池原理:(理解)
//模拟数据库连接池的原理 public class ConnectionPoolDemo { private static List<Connection> pool = new ArrayList<Connection>(); static{ try { for(int i=0;i<10;i++){ Connection conn = JdbcUtil.getConnection();//创建的新连接 pool.add(conn); } } catch (Exception e) { e.printStackTrace(); } } //从池中取出一个链接 public synchronized static Connection getConnection(){ if(pool.size()>0){ Connection conn = pool.remove(0); return conn; }else{ throw new RuntimeException("服务器真忙"); } } //把链接还回池中 public static void release(Connection conn){ pool.add(conn); } }
二、编写数据源(DataSource)(很重要)
编写一个类实现javax.sql.DataSource
public class MyDataSource1 implements DataSource { private static List<Connection> pool = Collections.synchronizedList(new ArrayList<Connection>()); static{ try { for(int i=0;i<10;i++){ Connection conn = JdbcUtil.getConnection();//创建的新连接 pool.add(conn); } } catch (Exception e) { e.printStackTrace(); } } //从池中获取链接 > com.mysql.jdbc.Connection public Connection getConnection() throws SQLException { if(pool.size()>0){ Connection conn = pool.remove(0); MyConnection1 mconn = new MyConnection1(conn,pool); return mconn; }else{ throw new RuntimeException("服务器真忙"); } }
public PrintWriter getLogWriter() throws SQLException { return null; } public void setLogWriter(PrintWriter out) throws SQLException { } public void setLoginTimeout(int seconds) throws SQLException { } public int getLoginTimeout() throws SQLException { return 0; } public <T> T unwrap(Class<T> iface) throws SQLException { return null; } public boolean isWrapperFor(Class<?> iface) throws SQLException { return false; } public Connection getConnection(String username, String password) throws SQLException { return null; } }
三、编程的难点:(设计模式)
难点:用一个实现了javax.sql.DataSource类的实例时,用户如果调用Connection.close()方法,会把链接关闭,失去了连接池的意义。
明确一个问题:用户得到Connection的实现是:数据库驱动对Connection接口的实现。因此,调用的close方法都是数据库驱动的,它会把链接给关闭。(这不是我们要的,我们要把该链接换回池中)。
解决方案:改写驱动原有的close方法。对已知类的某个/某些方法进行功能上的改变,有以下几种编码方案:
a、继承:此处行不通。
到底针对哪个驱动的实现写子类(很多)
数据库驱动对Connection接口的实现类,不允许被继承
丢失了原有对象的信息。捡了芝麻丢了西瓜。
b、装饰(包装)设计模式:(基础IO)
保持被包装对象的原有信息,又可以对某个/某些方法进行改写。
口诀:
1、编写一个类,实现与被包装类(数据库驱动对Connection的实现)相同的接口。(使这个类和数据库的驱动实现有着相同的行为)
2、定义一个变量,引用被包装类的实例。
3、定义构造方法,传入被包装类的实例。
4、对于要改写的方法,编写自己的代码即可。
5、对于不需要改写的方法,调用原有对象的对应方法。
//目前要包装的是:com.mysql.jdbc.Connection //1、编写一个类,实现与被包装类(数据库驱动对Connection的实现)相同的接口。(使这个类和数据库的驱动实现有着相同的行为) public class MyConnection implements Connection { // 2、定义一个变量,引用被包装类的实例 private Connection conn;//引用具体的数据库驱动 private List<Connection> pool; // 3、定义构造方法,传入被包装类的实例。 public MyConnection(Connection conn,List<Connection> pool){//依赖注入 this.conn = conn; this.pool = pool; } //把链接还回池中 // 4、对于要改写的方法,编写自己的代码即可。 public void close() throws SQLException { pool.add(conn); } public Statement createStatement() throws SQLException { return conn.createStatement(); } //5、对于不需要改写的方法,调用原有对象的对应方法。 public <T> T unwrap(Class<T> iface) throws SQLException { return conn.unwrap(iface); }
c、默认适配器:(为了后来做准备)
//默认的适配器 /* 本身也是一个包装类,但并没有对任何的方法进行改写 1、编写一个类,实现与被包装类(数据库驱动对Connection的实现)相同的接口。(使这个类和数据库的驱动实现有着相同的行为) 2、定义一个变量,引用被包装类的实例。 3、定义构造方法,传入被包装类的实例。 4、全部调用原有对象的对应方法 */ public class ConnectionAdapter implements Connection { private Connection conn; public ConnectionAdapter(Connection conn){ this.conn = conn; } public <T> T unwrap(Class<T> iface) throws SQLException { return conn.unwrap(iface); } public boolean isWrapperFor(Class<?> iface) throws SQLException { return conn.isWrapperFor(iface); }
/* 这也是包装:对ConnectionAdapter进行包装。 包装类即是被包装类的包装,又是他的子类。 1、编写一个类,继承已经是包装类的类。 2、定义一个变量,引用被包装类的实例。 3、定义构造方法,传入被包装类的实例。 4、覆盖掉需要改写的方法 */ public class MyConnection1 extends ConnectionAdapter { private Connection conn; private List<Connection> pool; public MyConnection1(Connection conn,List<Connection> pool){ super(conn); this.conn = conn; this.pool = pool; } public void close() throws SQLException { pool.add(conn); } }
d、动态代理:(很重要 AOP--Aspect-Oriented Programming 核心技术)
l 基于接口的动态代理:Proxy
如果一个类没有实现任何的接口,此种代理就不能使用了。
package com.itheima.proxy; public interface Human { void sing(float money); void dance(float money); }
package com.itheima.proxy; public class SpringBrother implements Human { public void sing(float money) { System.out.println("拿到钱:"+money+"开唱"); } public void dance(float money) { System.out.println("拿到钱:"+money+"开跳"); } }
package com.itheima.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Client1 { public static void main(String[] args) { final Human sb = new SpringBrother(); //代理人:如何动态产生代理人 /* ClassLoader loader:动态代理,必须有字节码class。加到内存中运行,必须有类加载器。固定:和被代理人用的是一样的 Class<?>[] interfaces:代理类要实现的接口,要和被代理对象有着相同的行为。固定:和被代理人用的是一样的 InvocationHandler h:如何代理。他是一个接口。策略设计模式。 */ //产生代理类,得到他的实例 Human proxyMan = (Human)Proxy.newProxyInstance(sb.getClass().getClassLoader(), sb.getClass().getInterfaces(), new InvocationHandler() { //匿名内部类,完成具体的代理策略 //调用代理类的任何方法,都会经过该方法。 拦截 /* Object proxy:对代理对象的引用。 Method method:当前执行的方法 Object[] args:当前方法用到的参数 返回值:当前调用的方法的返回值 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //判断出场费 if("sing".equals(method.getName())){ //唱歌 float money = (Float)args[0]; if(money>10000){ method.invoke(sb, money/2); } } if("dance".equals(method.getName())){ //唱歌 float money = (Float)args[0]; if(money>20000){ method.invoke(sb, money/2); } } return null; } } ); proxyMan.sing(20000); proxyMan.dance(100000); } }
l 基于子类的动态代理:CGLIB
前提:被代理类的要求
1、不能是final的
2、必须是public的
package com.itheima.cglib; public class SpringBrother{ public void sing(float money) { System.out.println("拿到钱:"+money+"开唱"); } public void dance(float money) { System.out.println("拿到钱:"+money+"开跳"); } }
package com.itheima.cglib; import java.lang.reflect.Method; import net.sf.cglib.proxy.Callback; import net.sf.cglib.proxy.Enhancer; import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; public class Client1 { public static void main(String[] args) { final SpringBrother sb = new SpringBrother(); //产生sb的代理: /* Class type:代理类的父类型 Callback cb:回调,如何代理 */ SpringBrother proxy = (SpringBrother) Enhancer.create(SpringBrother.class,new MethodInterceptor(){ public Object intercept(Object proxy, Method method, Object[] args, MethodProxy arg3) throws Throwable { //判断出场费 if("sing".equals(method.getName())){ //唱歌 float money = (Float)args[0]; if(money>10000){ method.invoke(sb, money/2); } } if("dance".equals(method.getName())){ //唱歌 float money = (Float)args[0]; if(money>20000){ method.invoke(sb, money/2); } } return null; } }); System.out.println(proxy instanceof SpringBrother); proxy.dance(100000); proxy.sing(50000); } }
比如普通的JavaBean就可能没有实现任何的接口。代理类是被代理类的子类。
四、开源数据源的使用:(很重要,非常简单)
1、DBCP:
Apache组织开发的。DBCP:DataBase Connection Pool,对数据源的一种实现。
a、拷贝jar包
b、编写配置文件
dbcpconfig.properties
#连接设置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test username=root password=sorry #<!-- 初始化连接 --> initialSize=10 #最大连接数量 maxActive=50 #<!-- 最大空闲连接 --> maxIdle=20 #<!-- 最小空闲连接 --> minIdle=5 #<!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:[属性名=property;] #注意:"user" 与 "password" 两个属性会被明确地传递,因此这里不需要包含他们。 connectionProperties=useUnicode=true;characterEncoding=utf8 #指定由连接池所创建的连接的自动提交(auto-commit)状态。 defaultAutoCommit=true #driver default 指定由连接池所创建的连接的只读(read-only)状态。 #如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix) defaultReadOnly= #driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。 #可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=REPEATABLE_READ
c、使用即可
public class DBCPUtil { private static DataSource dataSource; static{ try { InputStream in = DBCPUtil.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"); Properties props = new Properties(); props.load(in); dataSource = BasicDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static DataSource getDataSource(){ return dataSource; } public static Connection getConnection(){ try { return dataSource.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } }
2、C3P0:
开源数据源的实现。
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:///test</property> <property name="user">root</property> <property name="password">sorry</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> <named-config name="day15"> <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> </named-config> </c3p0-config>
public class C3P0Util { private static ComboPooledDataSource dataSource = new ComboPooledDataSource(); public static DataSource getDataSource(){ return dataSource; } public static Connection getConnection(){ try { return dataSource.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } }
3、更接近实际开发:JNDI管理数据源
JNDI:Java Naming and Directory Interface。属于JavaEE技术之一,目的模仿window系统中的注册表。
a、在服务器中注册JNDI数据源
1、拷贝数据库的驱动到Tomcat\lib目录下
2、在web应用的META-INF目录下建立一个名称为context.xml的配置文件
<?xml version="1.0" encoding="UTF-8"?> <Context> <Resource name="jdbc/test" auth="Container" type="javax.sql.DataSource" maxActive="20" maxIdle="5" maxWait="10000" username="root" password="sorry" driverClassName="com.mysql.jdbc.Driver" url="jdbc:mysql://localhost:3306/test"/> </Context>
3、获取JNDI容器中的资源
public class JndiDsUtil { public static Connection getConnection() throws Exception { Context initContext = new InitialContext(); DataSource ds = (DataSource) initContext .lookup("java:/comp/env/jdbc/test"); Connection conn = ds.getConnection(); return conn; } }
五、编写自己的JDBC框架(为学习DBUtil框架、Spring JDBCTemplate做准备)
1、数据库元信息的获取(为写框架而准备)
元信息:数据库的一些定义信息。比如用的是什么数据库等,表的定义信息等。
DatabaseMetaData PreparedStatement ResultSetMetaData getColumnCount
//数据库元信息的获取 public class Demo { //数据库本身信息的获取 @Test public void test1() throws Exception{ Connection conn = DBCPUtil.getConnection(); DatabaseMetaData dmd = conn.getMetaData(); String name = dmd.getDatabaseProductName();//能知道说什么方言 System.out.println(name); int isolation = dmd.getDefaultTransactionIsolation(); System.out.println(isolation); } //参数元数据信息:PreparedStatement时 @Test public void test2() throws Exception{ Connection conn = DBCPUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement("??????????"); ParameterMetaData pmd = stmt.getParameterMetaData(); int count = pmd.getParameterCount(); System.out.println(count);//统计语句中的占位符个数 } //结果集元数据信息: @Test public void test3()throws Exception{ Connection conn = DBCPUtil.getConnection(); PreparedStatement stmt = conn.prepareStatement("select * from account"); ResultSet rs = stmt.executeQuery(); ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount();//有几列 System.out.println(count); for(int i=0;i<count;i++){ String fieldName = rsmd.getColumnName(i+1); int type = rsmd.getColumnType(i+1); System.out.println(fieldName+":"+type); } } }
2、编写JDBC框架:(策略设计模式)
/** * 框架的核心类 * @author wzhting * */ public class DBAssist { private DataSource dataSource; public DBAssist(DataSource dataSource){ this.dataSource = dataSource; } //写:添加、删除、修改 //params参数要和sql中的占位符对应 public void update(String sql,Object...params) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ conn = dataSource.getConnection(); stmt = conn.prepareStatement(sql); //设置参数 //得到sql中的参数 ParameterMetaData pmd = stmt.getParameterMetaData(); int count = pmd.getParameterCount(); if(count>0){ if(params==null){ throw new RuntimeException("必须传入参数的值"); } if(count!=params.length){ throw new RuntimeException("参数数量不匹配"); } for(int i=0;i<count;i++){ stmt.setObject(i+1, params[i]); } } stmt.executeUpdate(); }catch(Exception e){ throw new RuntimeException(e); }finally{ release(rs, stmt, conn); } } //读:查询 public Object query(String sql,ResultSetHandler rsh,Object...params) { Connection conn = null; PreparedStatement stmt = null; ResultSet rs = null; try{ conn = dataSource.getConnection(); stmt = conn.prepareStatement(sql); //设置参数 //得到sql中的参数 ParameterMetaData pmd = stmt.getParameterMetaData(); int count = pmd.getParameterCount(); if(count>0){ if(params==null){ throw new RuntimeException("必须传入参数的值"); } if(count!=params.length){ throw new RuntimeException("参数数量不匹配"); } for(int i=0;i<count;i++){ stmt.setObject(i+1, params[i]); } } rs = stmt.executeQuery(); //有结果集,要封装到对象中。策略设计模式 return rsh.handle(rs); }catch(Exception e){ throw new RuntimeException(e); }finally{ release(rs, stmt, conn); } } private 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 interface ResultSetHandler { /** * 把结果中的数据封装到指定的对象中 * @param rs * @return 封装了数据的对象 */ Object handle(ResultSet rs); }
/** * 适合只有一条查询结果的情况 * 封装到JavaBean中 * 满足约定:数据库字段名和JavaBean字段名保持一致 * @author wzhting * */ public class BeanHanlder implements ResultSetHandler { private Class clazz;//目标类型 public BeanHanlder(Class clazz){ this.clazz = clazz; } public Object handle(ResultSet rs) { try { if(rs.next()){ //有记录 Object bean = clazz.newInstance();//目标对象 //有多少列,列名和值又是什么? ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount();//列数 for(int i=0;i<count;i++){ String fieldName = rsmd.getColumnName(i+1);//得到数据库字段名,也就得到了JavaBan的字段名 Object fieldValue = rs.getObject(fieldName);//字段值 //通过字段反射 Field f = clazz.getDeclaredField(fieldName); f.setAccessible(true); f.set(bean, fieldValue); } return bean; } return null; } catch (Exception e) { throw new RuntimeException(e); } } }
/** * 封装到JavaBean中 * 满足约定:数据库字段名和JavaBean字段名保持一致 * @author wzhting * */ public class BeanListHanlder implements ResultSetHandler { private Class clazz;//目标类型 public BeanListHanlder(Class clazz){ this.clazz = clazz; } public Object handle(ResultSet rs) { try { List list = new ArrayList(); while(rs.next()){ //有记录 Object bean = clazz.newInstance();//目标对象 //有多少列,列名和值又是什么? ResultSetMetaData rsmd = rs.getMetaData(); int count = rsmd.getColumnCount();//列数 for(int i=0;i<count;i++){ String fieldName = rsmd.getColumnName(i+1);//得到数据库字段名,也就得到了JavaBan的字段名 Object fieldValue = rs.getObject(fieldName);//字段值 //通过字段反射 Field f = clazz.getDeclaredField(fieldName); f.setAccessible(true); f.set(bean, fieldValue); } list.add(bean); } return list; } catch (Exception e) { throw new RuntimeException(e); } } }