Java——Web开发之事务与数据库连接池

事务:指的是一组操作,里面包含许多个单一的逻辑,只要有一个逻辑没有执行成功,那么都算失败,所有的数据都回到最初的状态。事务在默认情况下是自动提交的。(事务指针对连接对象)

 

1.事务的简单使用

1).关闭自动提交的设置 conn.setAutoCommit(false)
2).提交事务 conn.commit
3).回滚事务 conn.rollback

 

package web.stu;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import web.stu.util.JDBCUtil;

public class Transaction {
	
public static void main(String[] args){
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			conn = JDBCUtil.getConn();
			
			//连接,事务默认就是自动提交的。 关闭自动提交。
			conn.setAutoCommit(false);
			
			String sql = "update account set money = money - ? where id = ?";
			ps = conn.prepareStatement(sql);
		
			ps.setInt(1, 100);
			ps.setInt(2, 1);
			ps.executeUpdate();
			
			//int a = 10 /0 ;
			
			ps.setInt(1, -100);
			ps.setInt(2, 2);
			ps.executeUpdate();
			
			//成功: 提交事务。
			conn.commit();
			
		} catch (SQLException e) {
			try {
				//事变: 回滚事务
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
			
		}finally {
			JDBCUtil.release(conn, ps, rs);
		}
	}
}

2.事务的特性:ACID

  • 原子性:事务中包含的逻辑不可分割。
  • 一致性:事务执行的前后,数据的完整性保持一致。
  • 隔离性:事务在执行期间不受到其他事务的影响。
  • 持久性:事务执行成功,则数据应该持久保存到磁盘上。

 

3.事务的安全隐患:

  • 读产生的问题:
  1. 读脏数据:一个事务读到另外一个事务还未提交的数据。
  2. 不可重复读:一个事务读到另外一个事务提交的数据,造成前后两次查询结果不一致。
  3. 幻读:一个事务读到了另一个事务已提交的插入数据,导致多次查询结果不一样。
  • 隔离级别:
  1. 读未提交:会引起脏读的问题。
  2. 读已提交:能解决脏读的问题,但引发不可重复读。
  3. 可重复读:能解决脏读,不可重复读的问题,但是不能解决幻读。
  4. 可串行化:能解决脏读,不可重复读,幻读的问题。

     mysql默认的隔离级别是可重复读

     Oracle默认的隔离级别是读已提交

  • 写产生的问题:丢失更新
  • 解决丢失更新的方法:
  1. 悲观锁:可以在查询的时候加入for update
  2. 乐观锁:要求程序员自己控制

 

数据库连接池:

1)数据库的连接对象创建工作,比较消耗性能。
2)一开始先在内存中开辟一块空间(集合),一开始先往里面放置多个连接对象。后面如果需要连接, 直接从里面取,不要自己创建连接。使用完毕后,要归还连接,确保连接对象可以循环利用。
 

1.自己模拟数据库连接池的简单创建和使用

  • 先往池子里面放10个连接
  1. 开始创建10个连接
  2. 来的程序通过getConnection获取连接
  3. 用完之后,使用back归还连接
  4. 扩容
  • 使用这种方法(MyDataSource.java)会产生的问题:
  1. 需要额外记住back归还方法。
  2. 没法实现单例模式,导致不断地new对象。
  3. 无法面向接口编程,因为dataSource接口里面没有定义back方法,导致没法调用。

MyDataSource.java

package util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MyDataSource implements DataSource{

	//先往池子里面放10个连接
	List list =new ArrayList();
	public MyDataSource(){
		for (int i = 0; i < 10; i++) {
			Connection conn=JDBCUtil.getConn();
			list.add(conn);
		}
	}
	
	//连接池对外公布的获取连接的方法
	@Override
	public Connection getConnection() throws SQLException {
		//来拿连接时,先判断池子有没有连接,如果没有就扩容
		if(list.size()==0){
			for (int i = 0; i < 10; i++) {
				Connection conn=JDBCUtil.getConn();
				list.add(conn);
			}
		}
		//移除的是集合中的第一个元素 
		Connection conn=list.remove(0);
		return conn;
	}
	//用完之后,归还到池子里
	public void back(Connection conn){
		list.add(conn);
	}
	@Override
	public PrintWriter getLogWriter() throws SQLException {
		
		return null;
	}
	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
	}
	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
	}
	@Override
	public int getLoginTimeout() throws SQLException {
		
		return 0;
	}
	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {
		
		return null;
	}
	@Override
	public  T unwrap(Class iface) throws SQLException {
		
		return null;
	}
	@Override
	public boolean isWrapperFor(Class iface) throws SQLException {
		
		return false;
	}
	@Override
	public Connection getConnection(String username, String password)
			throws SQLException {
		
		return null;
	}
}

TestPool.java

package util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.junit.Test;

public class TestPool {
	@Test
	public void testPool(){
		Connection conn=null;
		PreparedStatement ps=null;
		MyDataSource dataSource=new MyDataSource();
		try {
			conn=dataSource.getConnection();
			String sql="insert into account values (null,'qq',2000)";
			ps=conn.prepareStatement(sql);
			ps.executeUpdate();
			
		} catch (SQLException e) {
			
			e.printStackTrace();
		}finally{
			try {
				ps.close();
			} catch (SQLException e) {
				
				e.printStackTrace();
			}
			//归还连接
			dataSource.back(conn);	
		}
	}	
}

 

  •  解决方法:修改接口中的close方法。原来的Connection对象的close方法是真的关闭连接,改成以后关闭时,是一个归还连接的方法。
  •  如何扩展一个方法:
  1.  直接改源码,无法实现。
  2.  使用继承,无法知道实现Connection接口的类,所以也不行。
  3.  使用装饰者模式。(在这使用)
  4.  动态代理。

使用装饰者模式解决后的代码~

MyDataSource2.java

package util;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Logger;
import javax.sql.DataSource;
public class MyDataSource2 implements DataSource{
	//先往池子里面放10个连接
	List list =new ArrayList();
	public MyDataSource2(){
		for (int i = 0; i < 10; i++) {
			Connection conn=JDBCUtil.getConn();
			list.add(conn);
		}
	}
	//连接池对外公布的获取连接的方法
	@Override
	public Connection getConnection() throws SQLException {
		//来拿连接时,先判断池子有没有连接,如果没有就扩容
		if(list.size()==0){
			for (int i = 0; i < 10; i++) {
				Connection conn=JDBCUtil.getConn();
				list.add(conn);
			}
		}
		//移除的是集合中的第一个元素 
		Connection conn=list.remove(0);
		//conn.close();	//直接调用自己扩展的,直接可以归还	
		//在把这个对象抛出去的时候,对这个对象进行包装
		Connection connection=new ConnectionWrap(conn,list);
		return connection;
	}
	//用完之后,归还到池子里
	public void back(Connection conn){
		list.add(conn);
	}
	@Override
	public PrintWriter getLogWriter() throws SQLException {
		
		return null;
	}
	@Override
	public void setLogWriter(PrintWriter out) throws SQLException {
	}
	@Override
	public void setLoginTimeout(int seconds) throws SQLException {
	}
	@Override
	public int getLoginTimeout() throws SQLException {
		return 0;
	}
	@Override
	public Logger getParentLogger() throws SQLFeatureNotSupportedException {	
		return null;
	}
	@Override
	public  T unwrap(Class iface) throws SQLException {
		return null;
	}
	@Override
	public boolean isWrapperFor(Class iface) throws SQLException {
		return false;
	}
	@Override
	public Connection getConnection(String username, String password)
			throws SQLException {
		return null;
	}
}

ConnectionWrap.java需要实现Connection接口,重写close方法,因为代码太长,所以只贴出部分代码截图

Java——Web开发之事务与数据库连接池_第1张图片

 

测试代码TestPool2.java

package util;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

import org.junit.Test;

public class TestPool2 {
	@Test
	public void testPool(){
		Connection conn=null;
		PreparedStatement ps=null;
		MyDataSource dataSource=new MyDataSource();
		try {
			conn=dataSource.getConnection();
			String sql="insert into account values (null,'qq',2000)";
			ps=conn.prepareStatement(sql);
			ps.executeUpdate();
			
		} catch (SQLException e) {
			
			e.printStackTrace();
		}finally{
			try {
				ps.close();
			} catch (SQLException e) {
				
				e.printStackTrace();
			}
			JDBCUtil.release(conn, ps);
		}
	}	
}

使用到之前的JDBCUtil.java代码

package util;
import java.io.FileInputStream;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

public class JDBCUtil {
	static String driverClass = null;
	static String url = null;
	static String name = null;
	static String password= null;
	static{
		try {
			//1. 创建一个属性配置对象
			Properties properties = new Properties();
			InputStream is = new FileInputStream("jdbc.properties");	//Java工程里可以直接new来加载配置文件
			
			//使用类加载器,去读取src底下的资源文件。 后面在servlet,web工程需要使用类加载器来加载配置文件
			//InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
			//导入输入流。
			properties.load(is);
			
			//读取属性
			driverClass = properties.getProperty("driverClass");
			url = properties.getProperty("url");
			name = properties.getProperty("name");
			password = properties.getProperty("password");
				
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 获取连接对象
	 * @return
	 */
	public static Connection getConn(){
		Connection conn = null;
		try {
			Class.forName(driverClass);
			//静态代码块 ---> 类加载了,就执行。 java.sql.DriverManager.registerDriver(new Driver());
			//DriverManager.registerDriver(new com.mysql.jdbc.Driver());
			//DriverManager.getConnection("jdbc:mysql://localhost/test?user=monty&password=greatsqldb");
			//2. 建立连接 参数一: 协议 + 访问的数据库 , 参数二: 用户名 , 参数三: 密码。
			conn = DriverManager.getConnection(url, name, password);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return conn;
	}
	
	/**
	 * 释放资源
	 */
	public static void release(Connection conn , Statement st , ResultSet rs){
		closeRs(rs);
		closeSt(st);
		closeConn(conn);
	}
	public static void release(Connection conn , Statement st){
		closeSt(st);
		closeConn(conn);
	}
	private static void closeRs(ResultSet rs){
		try {
			if(rs != null){
				rs.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			rs = null;
		}
	}
	private static void closeSt(Statement st){
		try {
			if(st != null){
				st.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			st = null;
		}
	}
	private static void closeConn(Connection conn){
		try {
			if(conn != null){
				conn.close();
			}
		} catch (SQLException e) {
			e.printStackTrace();
		}finally{
			conn = null;
		}
	}
}

注:在上面这个代码需要注意一下Properties的创建,对于java工程和web工程有不同的加载方式,在下面给出部分代码~

  • 创建一个属性配置对象

            Properties properties = new Properties();
            InputStream is = new FileInputStream("jdbc.properties");    //Java工程里可以直接new来加载配置文件
            
            使用类加载器,去读取src底下的资源文件。 后面在servlet,web工程需要使用类加载器来加载配置文件
            InputStream is = JDBCUtil.class.getClassLoader().getResourceAsStream("jdbc.properties");
            导入输入流。
            properties.load(is);

 

你可能感兴趣的:(Javaweb_)