之前写的 数据库连接JDBC工具类只是最简单的版本,接下来继续进行升级。。。
这个版本是数据库连接池(根据底层原理自己写的)的第一个版本:
数据库连接池---v1---用户需要手动调用Conn2Utils.back(con)把连接还回池中:
首先, 池我们一般都用集合来做,于是定义如下:
//创建一个池
private static List<Connection> pool = new ArrayList<Connection>();
private static final int NUM = 3;
接下来就是静态池:
static {
try {
Properties p = new Properties();
p.load(Conn2Utils.class.getClassLoader().getResourceAsStream(
"jdbc.properties"));
//String driver = p.getProperty("driver");
String url = p.getProperty("url");
String user = p.getProperty("username");
String pwd = p.getProperty("password");
for (int i = 0; i < NUM; i++) {
Connection conn = DriverManager.getConnection(url, user, pwd);
pool.add(conn);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
通过Proerties类 加载数据库的配置信息,并将Connection 对象以下简称conn 放入池子中。
然后是给用户获取Conn的静态方法,因为是多线程,因此要加锁,当池里面没有了的时候,睡1秒,当池子里有的时候拿走。
public static synchronized Connection getConn() {
if (pool.size() <=0) {
System.out.println("池中已经没有了,请稍候再连");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getConn();
}
return pool.remove(0);
}
用户需要手动调用Conn2Utils.back(con)把连接还回池中:
public static void back(Connection conn){
System.out.println("还回来一个con...");
pool.add(conn);
}
通过多线程开启事务的模拟用户操作:
//开启事务con.setAutoCommit(false);
//使用JUnit测试多线程时的一个细节: 当测试主线程执行完时,虚拟机自动会把在它(测试
主线程)内部新开的子线程全部中断
package cn.hncu.tx;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import org.junit.Test;
import cn.hncu.pub.ConnUtils;
public class TxDemo1 {
@Test//没有事务
public void save1() throws Exception{
Connection con = ConnUtils.getConn();
Statement st=con.createStatement();
st.execute("insert into stud2 values('A090','Jack')");
st.execute("insert into stud2 values('A090','Rose')"); //该句出异常,后续的正确sql语句不会执行
st.execute("insert into stud2 values('A092','Tom')");
con.close();
}
@Test//没有事务
public void save2() throws Exception{
Connection con = ConnUtils.getConn();
Statement st = con.createStatement();
st.addBatch("insert into stud values('A090','Jack')");
st.addBatch("insert into stud values('A090','Rose')"); //该句出异常,后续的正确sql语句仍然会执行
st.addBatch("insert into stud values('A092','Tom')");
st.executeBatch();
con.close();
}
@Test//事务的简单模板
public void save3(){
Connection con = ConnUtils.getConn();
try{
//开启事务
con.setAutoCommit(false);
//执行带事务的sql语句块
Statement st = con.createStatement();
st.execute("insert into stud values('A090','Jack')");
st.execute("insert into stud values('A091','Rose')"); //该句出异常,后续的正确sql语句不会执行
st.execute("insert into stud values('A092','Tom')");
con.commit();
System.out.println("事务提交了....");
}catch (Exception e) {
System.out.println("事务回滚了....");
try {
con.rollback();
} catch (SQLException e1) {
throw new RuntimeException(e1.getMessage(), e1);
}
}finally{
try {
con.setAutoCommit(true);//还原现场
con.close();
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
[点击并拖拽以移动]
结果:
V1版本缺点很明显,接下来是V2版本:
数据库连接池---v2---用户直接调用con.close()就可以实现还回池中
---采用装饰设置模式增强conn
第一步还是定义池:
// 创建一个池
private static List<Connection> pool = new ArrayList<Connection>();
private final static int NUM = 3;
第二步是:
利用装饰模式,把conn改成它的子类对象conn2放入池中,同时在子类中把close()方法拦截(覆盖)并做成把连接
还回池中的动作:
MyConnection conn2 = new MyConnection(conn);// 可写内部类
pool.add(conn2);
static {
try {
Properties p = new Properties();
p.load(Conn3Utils.class.getClassLoader().getResourceAsStream(
"jdbc.properties"));
String url = p.getProperty("url");
String user = p.getProperty("username");
String pwd = p.getProperty("password");
for (int i = -0; i < NUM; i++) {
Connection conn = DriverManager.getConnection(url, user, pwd);
// 利用装饰模式,把conn改成它的子类对象conn2放入池中,同时在子类中把close()方法拦截(覆盖)并做成把连接还回池中的动作
MyConnection conn2 = new MyConnection(conn);// 可写内部类
pool.add(conn2);
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static synchronized Connection getConn() {
if (pool.size() <= 0) {
System.out.println("池中已经没有了,请稍候再连");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e.getMessage(), e);
}
return getConn();
}
System.out.println("从池中拿走一个...");
return pool.remove(0);
}
第三步是MyConnection 类:
实现Connection接口:
构造传参将CONN对象拿到
重点是覆盖close方法:
@Override
public void close() throws SQLException {
// 还回池中
System.out.println("还回来一个conn...");
pool.add(this);
}
其他方法都是用包装的conn对象去执行
static class MyConnection implements Connection {
private Connection conn = null;
public MyConnection(Connection conn) {
this.conn = conn;
}
@Override
public void close() throws SQLException {
// 还回池中
System.out.println("还回来一个conn...");
pool.add(this);
}
// ///以下方法的功能全是用原Connection默认的,即直接用包装的conn对象去执行/
@Override
public <T> T unwrap(Class<T> iface) throws SQLException {
return conn.unwrap(iface);
}
@Override
public boolean isWrapperFor(Class<?> iface) throws SQLException {
return conn.isWrapperFor(iface);
}
@Override
public Statement createStatement() throws SQLException {
return conn.createStatement();
}
以下覆盖方法太多,同上
......
结果:
觉得上面的版本已经很好了是么? 当然不是哈哈,接下来的版本3,写的更活。
不觉得实现Connection的接口要写的东西太多了吗,真正要用到的只有close方法,
接下来就引入动态代理模式:
具体的动态代理见我的另一篇博客:
https://blog.csdn.net/lx678111/article/details/82818634
JDBC工具类V4版本:
利用动态代理模式,把conn改成它的代理后的对象conn2放入池中,同时在代理拦截
中把close()方法拦截并做成把它还回池中的动作。
技术入口: Object proxyObj2=Proxy.newProxyInstance(loader, interfaces, h);
for(int i=0;i<NUM;i++){
final Connection conn=DriverManager.getConnection(url, user, pwd);
//利用动态代理模式,把conn改成它的代理后的对象conn2放入池中,同时在代理拦截中把close()方法拦截并做成把它还回池中的动作
Object proxyObj=Proxy.newProxyInstance(
Conn4Utils.class.getClassLoader(),
//conn.getClass().getInterfaces(), //坑:不能替代下一行,因为mysql中使用了自己的类加载器
new Class[]{Connection.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxyObj, Method method, Object[] args)
throws Throwable {
//把close方法拦截并更改成把“代理后的对象”放回池中的动作
if("close".equals(method.getName())){
pool.add((Connection) proxyObj);
System.out.println("往池中还回来一个连接。。。");
return null; //跳过下面的放行,这样原有的close()功能就没了
}
return method.invoke(conn, args);//放行
}
});
Connection conn2=(Connection) proxyObj;
pool.add(conn2);
//pool.add((Connection) proxiedObj); //代替上面两行
Conn4Utils:完整代码:
package cn.hncu.pub;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
public class Conn4Utils {
private static List<Connection> pool=new ArrayList<Connection>();
private final static int NUM=3;
static{
try {
Properties p=new Properties();
p.load(Conn4Utils.class.getClassLoader().getResourceAsStream("jdbc.properties"));
//String driver=p.getProperty("driver");
String url=p.getProperty("url");
String user=p.getProperty("username");
String pwd=p.getProperty("password");
//Class.forName(driver);
for(int i=0;i<NUM;i++){
final Connection conn=DriverManager.getConnection(url, user, pwd);
//利用动态代理模式,把conn改成它的代理后的对象conn2放入池中,同时在代理拦截中把close()方法拦截并做成把它还回池中的动作
Object proxyObj=Proxy.newProxyInstance(
Conn4Utils.class.getClassLoader(),
//conn.getClass().getInterfaces(), //坑:不能替代下一行,因为mysql中使用了自己的类加载器
new Class[]{Connection.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxyObj, Method method, Object[] args)
throws Throwable {
//把close方法拦截并更改成把“代理后的对象”放回池中的动作
if("close".equals(method.getName())){
pool.add((Connection) proxyObj);
System.out.println("往池中还回来一个连接。。。");
return null; //跳过下面的放行,这样原有的close()功能就没了
}
return method.invoke(conn, args);//放行
}
});
Connection conn2=(Connection) proxyObj;
pool.add(conn2);
//pool.add((Connection) proxiedObj); //代替上面两行
}
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
public static synchronized Connection getConn(){
if(pool.size()<=0){
System.out.println("池中已经没有了,请稍候再连");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return getConn();
}
System.out.println("从池中拿走一个...");
return pool.remove(0);
}
}
测试类:
package cn.hncu.tx;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import cn.hncu.pub.Conn4Utils;
public class TxDemo5 {
public static void main(String[] args) {
Connection conn = Conn4Utils.getConn();
try{
//开启事务
conn.setAutoCommit(false);
//执行带事务的sql语句块
Statement st = conn.createStatement();
st.execute("insert into stud3 values('A001','Jack')");
new OneThread(1).start();
new OneThread(2).start();
new OneThread(3).start();
new OneThread(4).start();
new OneThread(5).start();
new OneThread(7).start();
new OneThread(9).start();
new OneThread(11).start();
conn.commit();
System.out.println("主线程事务提交了....");
}catch (Exception e) {
System.out.println("主线程事务回滚了....");
try {
conn.rollback();
} catch (SQLException e1) {
throw new RuntimeException(e1.getMessage(), e1);
}
}finally{
try {
conn.setAutoCommit(true);//还原现场
conn.close(); //还回池中
} catch (SQLException e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
static class OneThread extends Thread{
private int num;
public OneThread(int num) {
super();
this.num = num;
}
/* (non-Javadoc)
* @see java.lang.Thread#run()
*/
@Override
public void run() {
Connection conn = Conn4Utils.getConn();
try {
conn.setAutoCommit(false);
//执行带事务的sql语句块
Statement st = conn.createStatement();
//st.execute("insert into stud values('B001','Tom')");
if(num%2==1){
st.execute("insert into stud3 values('B00"+num+"','Tom"+num+"')");
}else{
st.execute("insert into stud3 values('B00"+num+"','Tom"+num+")"); //错误的sql--用于故意演示事务回滚
}
conn.commit();
System.out.println("子线程"+num+"事务提交了....");
} catch (Exception e) {
System.out.println("子线程"+num+"事务回滚了....");
try {
conn.rollback();
} catch (SQLException e1) {
throw new RuntimeException(e.getMessage(), e);
}
}finally{
try {
conn.setAutoCommit(true);
conn.close(); //还回池中
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}
}
}
结果:
后面还有更新~~