在上次写的连接模板中,Connection是单例,只适用于单线程也就是一个用户操作,一旦多线程同时运行,就会挂掉,这里将探讨原因和解决办法。
单例工厂类的漏洞:
这里写了一个类,专门对此做了测试:
TxDemo.java
<span style="font-size:14px;">package cn.hncu.tx;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import cn.hncu.pubs.ConnFactory;
//使用的是单例连接工厂ConnFactory
public class TxDemo {
/*
* 我这里开了四个线程,加上mian线程,共五个线程,每次拿Connection都是拿的同一个对象,
* 则这些线程之间会串数据,如:
* 我让main线程执行插入,然后在提交之前睡眠,让第三个线程执行插入时出错,
* 然后回滚,就会把main线程刚才所执行的全部回滚,main线程之前的动作全部无效,
* 若其他线程比三线程提前执行完,则把main线程的动作也一起提交了,若main线程下面出错,则无法回滚
* 这样不安全
*/
public static void main(String[] args) {
Connection con = ConnFactory.getConn();
try {
Statement st = con.createStatement();
con.setAutoCommit(false);// 开启一个事务
st.executeUpdate("insert into person2 values('P07','赵子龙','1')");
MyThread th1 = new MyThread(1);
MyThread th2 = new MyThread(2);
MyThread th3 = new MyThread(3);
MyThread th4 = new MyThread(4);
th1.start();
th2.start();
th3.start();
th4.start();
Thread.sleep(1000);
con.commit();
System.out.println("main线程提交一个事务");
} catch (Exception e) {
try {
con.rollback();
System.out.println("main线程回滚一个事务");
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
// con.setAutoCommit(true);
// con.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class MyThread extends Thread {
private int num;
public MyThread(int num) {
this.num = num;
}
@Override
public void run() {
Connection con = ConnFactory.getConn();
try {
Statement st = con.createStatement();
con.setAutoCommit(false);// 开启一个事务
String sql = "insert into person2 values('P07" + num + "','赵子龙"
+ num + "','1')";
if(num==3){
sql = "insert into person2 values('P07" + num + ",'赵子龙"
+ num + "','1')";
}else{
Thread.sleep(100);
}
//System.out.println("sql-----" + sql);
st.executeUpdate(sql);
con.commit();
System.out.println(num + "线程提交一个事务");
} catch (Exception e) {
try {
con.rollback();
System.out.println(num + "线程回滚一个事务");
} catch (SQLException e1) {
e1.printStackTrace();
} finally {
try {
// con.setAutoCommit(true);
// con.close();
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
}
</span>
串数据:虽然main线程提交了事务,但是被3线程给回滚了,所以main线程做的插入动作也被回滚了,再提交也没用
解决:要开一个连接池,池中放连接,对池做维护
ConnUtils.java工具类
<span style="font-size:14px;">package cn.hncu.pool;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
//new出多个连接,放进池中,连接不再是单例,用池当单例
public class ConnUtils {
private static List<Connection> pool=new ArrayList<Connection>();
private final static int NUM=3;
private ConnUtils(){
}
static{
Properties p=new Properties();
try {
p.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));//可从本类ConnUtils.Class.getClassLoader().getResourceAsStream("jdbc.properties")拿
String dirver=p.getProperty("driver");
String url=p.getProperty("url");
String user=p.getProperty("username");
String password=p.getProperty("password");
Class.forName(dirver);
for(int i=0;i<NUM;i++){
Connection con=DriverManager.getConnection(url, user, password);
pool.add(con);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static synchronized Connection getConn(){//加把锁,防止有多条线程在这里等待
try {
if(pool.size()<=0){
System.out.println("池中没有连接了.....");
Thread.sleep(1000);
return getConn();
}
} catch (InterruptedException e) {
}
return pool.remove(0);
}
public static void back(Connection con){
System.out.println("向池中还回一个连接-----"+con);
pool.add(con);
}
}
</span>
TxDemo2.java测试类
<span style="font-size:14px;">package cn.hncu.tx;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import cn.hncu.pool.ConnUtils;
import cn.hncu.pubs.ConnFactory;
//使用的是多例连接工具ConnUtils
public class TxDemo2 {
public static void main(String[] args) {
Connection con = ConnUtils.getConn();
try {
Statement st = con.createStatement();
con.setAutoCommit(false);// 开启一个事务
st.executeUpdate("insert into person2 values('P07','赵子龙','1')");
MyThread2 th1 = new MyThread2(1);
MyThread2 th2 = new MyThread2(2);
MyThread2 th3 = new MyThread2(3);
MyThread2 th4 = new MyThread2(4);
th1.start();
th2.start();
th3.start();
th4.start();
Thread.sleep(1000);
con.commit();
System.out.println("main线程提交一个事务");
} catch (Exception e) {
try {
con.rollback();
System.out.println("main线程回滚一个事务");
} catch (SQLException e1) {
e1.printStackTrace();
}
} finally {
try {
con.setAutoCommit(true);
// con.close();
ConnUtils.back(con);//这里是回收Connection连接,应做成con.close(),池在背后回收资源,调用back方法
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
class MyThread2 extends Thread {
private int num;
public MyThread2(int num) {
this.num = num;
}
@Override
public void run() {
Connection con = ConnUtils.getConn();
try {
Statement st = con.createStatement();
con.setAutoCommit(false);// 开启一个事务
String sql = "insert into person2 values('P07" + num + "','赵子龙"
+ num + "','1')";
if(num==2){
sql = "insert into person2 values('P07" + num + ",'赵子龙"
+ num + "','1')";
}else{
Thread.sleep(100);
}
//System.out.println("sql-----" + sql);
st.executeUpdate(sql);
con.commit();
System.out.println(num + "线程提交一个事务");
} catch (Exception e) {
try {
con.rollback();
System.out.println(num + "线程回滚一个事务");
} catch (SQLException e1) {
e1.printStackTrace();
} finally {
try {
con.setAutoCommit(true);
// con.close();
ConnUtils.back(con);
} catch (Exception e1) {
e1.printStackTrace();
}
}
}
}
}
</span>
每个线程都独立开了,各不影响:
装修模式:
对Connection功能进行增强
ConnUtils2.java
由于本代码实现了Connection接口,代码较多,但大部分都是 不重要的,这里我只对改动的代码贴出来:
在静态块for循环中
for(int i=0;i<NUM;i++){
Connection conn=DriverManager.getConnection(url, user, password);
//更改con.close()方法
MyConnection con=new MyConnection(conn);
pool.add(con);
}
这里用到了MyConnection内部类
<span style="font-size:14px;">//采用包装模式(装修模式)对Connection con功能进行增强
//因为是静态块中new MyConnection 同时还要访问静态变量pool,所以要声明成静态,不然new 的时候会出现错误
static class MyConnection implements Connection {
private Connection con;
public MyConnection(Connection con){
this.con=con;
}
</span>
下面都是实现Connection抽象方法,我们只对close方法进行更改,其余还是用原来的
<span style="font-size:14px;">//功能增强就是这个地方,把close方法,改成放回池中
@Override
public void close() throws SQLException {
System.out.println("往池中还回一个连接------"+this);
pool.add(this);//注意这里要放this
}</span>
TxDemo3.java中也只是把原先的ConnUtils.back(con)方法,改成con.close()方法,其余与TxDemo2.java相同,这里不再重叙。
结果也是正确的:
代理模式:
代理模式的作用是:为其他对象提供一种代理以控制对这个对象的访问。
代理模式的初期引入:
房东与租客:
Renter.java
package cn.hncu.proxy.rent;
public class Renter implements IRenter {
@Override
public void rent(){
System.out.println("房东:租出房子,收取房租");
}
@Override
public String toString() {
return "好好保护我的房子....";
}
}
IRenter.java
package cn.hncu.proxy.rent;
public interface IRenter {
public abstract void rent();
}
Client.java
package cn.hncu.proxy.rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
final Renter r=new Renter();//被代理对象-----原型对象
Object newObj=Proxy.newProxyInstance(
//ClassLoader loader, Class<?>[] interfaces, InvocationHandler h
Client.class.getClassLoader(),
new Class[]{IRenter.class},
new InvocationHandler() {
@Override//参数:proxy是代理后的对象(和外面的newObj一样的),method是当前被调用的method对象,args是当前method对象的参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("rent")){
System.out.println("给中介交点费用...");
return method.invoke(r, args);
}
return method.invoke(r, args);//这里是放行,不做任何拦截,只是从这里过了一下
}
});
/*
* 代理后的对象newObj和原型对象r 是不同类型的对象。二者属于兄弟关系,都是IRenter接口的实现类(子类)
* 因此newObj是无法强转成Renter类型
*/
//代理后的newObj通常都是强转换成接口类型来使用
IRenter ir=(IRenter)newObj;
ir.rent();
String str=ir.toString();
System.out.println(str);
}
}
运行结果图:
连接池用代理模式:
ConnnUtils3.java
package cn.hncu.pool;
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;
//new出多个连接,放进池中,连接不再是单例,用池当单例
public class ConnUtils3 {
private static List<Connection> pool=new ArrayList<Connection>();
private final static int NUM=3;
private ConnUtils3(){
}
static{
Properties p=new Properties();
try {
p.load(ClassLoader.getSystemResourceAsStream("jdbc.properties"));//可从本类ConnUtils.Class.getClassLoader().getResourceAsStream("jdbc.properties")拿
String dirver=p.getProperty("driver");
String url=p.getProperty("url");
String user=p.getProperty("username");
String password=p.getProperty("password");
Class.forName(dirver);
for(int i=0;i<NUM;i++){
final Connection conn=DriverManager.getConnection(url, user, password);
//用代理模式生成一个增强Connection对象,把它的close方法拦截并改掉
Object newObj=Proxy.newProxyInstance(
//加载器
ConnUtils3.class.getClassLoader(),
//接口类型
new Class[]{Connection.class},
//拦截
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
if(method.getName().equals("close")){
pool.add((Connection)proxy);
return null;
}
return method.invoke(conn, args);
}
});
pool.add((Connection)newObj);
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static synchronized Connection getConn(){//加把锁,防止有多条线程在这里等待
try {
if(pool.size()<=0){
System.out.println("池中没有连接了.....");
Thread.sleep(1000);
return getConn();
}
} catch (InterruptedException e) {
}
return pool.remove(0);
}
}
测试类与测试结果和上次的一样,这里不再贴出。
通用模板:
ProxyUtils.java
package cn.hncu.proxy.util;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtils implements InvocationHandler {
private Object obj;
private ProxyUtils(Object obj){
this.obj=obj;
}
public static Object getProxy(Object obj){
Object newObj=Proxy.newProxyInstance(
ProxyUtils.class.getClassLoader(),
obj.getClass().getInterfaces(),
new ProxyUtils(obj));//构造传参
return newObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("前面拦拦:......");
Object resObj=method.invoke(obj, args);
System.out.println("后面拦拦:......");
return resObj;
}
}
测试模板:
人和狗
dog.java
package cn.hncu.proxy.util;
public class Dog implements IDog {
private String name;
public Dog(){
this.name="小黑";
}
/* (non-Javadoc)
* @see cn.hncu.proxy.util.IDog#bark()
*/
@Override
public void bark(){
System.out.println(name+": 汪``汪``汪``");
}
}
IDog.java
package cn.hncu.proxy.util;
public interface IDog {
public abstract void bark();
}
Person.java
package cn.hncu.proxy.util;
public class Person implements IPerson {
private String name;
public Person(String name){
this.name=name;
}
/* (non-Javadoc)
* @see cn.hncu.proxy.util.IPerson#sayHi()
*/
@Override
public void sayHi(){
System.out.println("Hello,I am "+name);
}
}
IPerson.java
package cn.hncu.proxy.util;
public interface IPerson {
public abstract void sayHi();
}
Client.java
package cn.hncu.proxy.util;
public class Client {
public static void main(String[] args) {
Dog dog=new Dog();
IDog dog2=(IDog) ProxyUtils.getProxy(dog);
Person p=new Person("二牛");
IPerson p2=(IPerson) ProxyUtils.getProxy(p);
p2.sayHi();
System.out.println("------------------------");
dog2.bark();
}
}
运行结果: