您的位置:首页 > 其它

web 学习笔记16-JDBC连接池 扩展已知类

2017-07-25 00:09 441 查看
1、连接池:

为什么要用连接池,如果你的服务器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包,其他的工程就可以使用了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: