玩转MySQL———自己写的 数据库连接池 结合事务

MySQL 专栏收录该内容
26 篇文章 0 订阅

之前写的 数据库连接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);
				}
				
			}
		}
    	
		
    }
}

结果:

 

后面还有更新~~

 

 

 

 

 

  • 0
    点赞
  • 0
    评论
  • 2
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 技术工厂 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值