事务,连接池,异常的使用

事务的概念
  1)每种数据库都有事务的支持,但支持强度不同
  2)以MySQL为例,
    启动事务
    start transaction;

    提交事务
    commit;

    回滚事务
    rollback;
  3)在事务范围内回滚是允许的,但如果commit后再回滚,无效   
  4)其实每条SQL都有事务存在,只是显示还隐藏而言,默认都是隐藏事务
  5)事务的操作,必须争对同一个Connection。    
  6)事务的操作,可以设置一个回滚点,便于回滚到最近的回滚点处。

JDBC显示操作事务的API

package cn.itcast.web.jdbc.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Savepoint;
import cn.itcast.web.jdbc.util.JdbcUtil;

//JDBC显示操作事务的API
public class Demo2 {
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		String sqlA = "update account set salary=salary-1000 where name='aaa'";
		String sqlB = "update account set salary=salary+1000 where name='bbb'";
		String sqlC = "insert into account(name,salary) values('ccc',3000)";
		Savepoint sp = null;
		
		try {
			
			conn = JdbcUtil.getMySqlConnection();
			
			//设置事务显示手工提交
			conn.setAutoCommit(false);
			
			//NO1
			pstmt = conn.prepareStatement(sqlA);
			pstmt.executeUpdate();
			
			//NO2
			pstmt = conn.prepareStatement(sqlB);
			pstmt.executeUpdate();

			//设置一个回滚点        回滚点一定要再出错的点前面
			sp = conn.setSavepoint();
			
			Integer.parseInt("abc");   //这个是故意设置的出错点

			//NO3
			pstmt = conn.prepareStatement(sqlC);
			pstmt.executeUpdate();
			
			//事务手工提交    提交后就无法回滚了
			conn.commit();
			
		} catch (Exception e) {
			e.printStackTrace();
			try {
				//事务回滚,默认情况下,回滚到事务开始之前的状态     也可以设置一个回滚点,然后就回滚到最近的回滚点
				conn.rollback(sp);   //使用默认回到开始情况的就不用带有参数
				conn.commit();
			} catch (Exception e1) {
				
			}
		}finally{
			JdbcUtil.close(rs);
			JdbcUtil.close(pstmt);
			JdbcUtil.close(conn);
		}
	}
}
JDBCUtils争对同一个Connection的处理方案
package cn.itcast.web.jdbc.util;

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;

//JDBC工具类:关闭流和取得连接
public final class JdbcUtil {
	
	private static String driver;
	private static String url;
	private static String user;
	private static String password;
	private static ThreadLocal<Connection> tl = new ThreadLocal<Connection>();   //局部线程变量
	
	//静态块:加载文件
	static{
		Properties props = new Properties();
		InputStream is = JdbcUtil.class.getClassLoader().getResourceAsStream("cn/itcast/web/jdbc/config/db.properties");
		try {
			props.load(is);
		} catch (Exception e) {
			e.printStackTrace();
		}
		driver = props.getProperty("driver");
		url = props.getProperty("url");
		user = props.getProperty("user");
		password = props.getProperty("password");
	}
	
	//静态块:注册驱动
	static{
		try {
			Class.forName(driver);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	//取得连接,每个线程都取得唯一的一个Connection对象
	public static Connection getMySqlConnection() {
		/*Connection conn = null;
		try {
			conn = DriverManager.getConnection(url,user,password);
		} catch (SQLException e) {
			e.printStackTrace();
		}
		*/
		Connection conn = tl.get();
		if(conn==null){
			try {
				//第一次该线程没有绑定connection对象
				conn = DriverManager.getConnection(url,user,password);
				//绑定到该线程中
				tl.set(conn);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		
		return conn;
	}
	//关闭连接	
	public static void close(ResultSet rs){
		if(rs!=null){
			try {
				rs.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	public static void close(Statement stmt){
		if(stmt!=null){
			try {
				stmt.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	public static void close(Connection conn){
		if(conn!=null){
			try {
				conn.close();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}
	//事务开始
	public static void begin(){
		Connection conn = getMySqlConnection();
		try {
			conn.setAutoCommit(false);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	//事务提交
	public static void commit()throws SQLException{
		Connection conn = getMySqlConnection();
		try {
			conn.commit();
		} catch (SQLException e) {
			e.printStackTrace();
			throw e;
		}
	}	
	//事务回滚
	public static void rollback() throws SQLException{
		Connection conn = getMySqlConnection();
		try {
			conn.rollback();
		} catch (SQLException e) {
			e.printStackTrace();
			throw e;
		}
	}
	public static void closeConnection()throws SQLException {
		Connection conn = getMySqlConnection();
		JdbcUtil.close(conn);
		//将该线程与所绑定的Connection对象分离
		tl.remove();
	}	
}



事务的特性
    1)原子性(A)事务是的各个操作是一个不可分割的子操作。必须将其看成一个整体,即原子操作
    2)一致性(C)事务前后,由一个一致状态转移到另一个一致状态
   *3)隔离性(I)事务中,每个线程操作同张表同记录时,相互分割
    4)持久性(D)事务一旦生效,在没有操作该记录时情况下,永远保持不变

三个缺点(违背隔离性)
   1)脏读:一个线程看到了另一个线程未提交的数据,叫脏读
   2)不可重复读:一个线程多次做查询操作,多次结果都不一致,叫不可重复读
    上述二项,强调的是查询,内容变,但数量不变
    
   3)幻读/虚读:
    上述一项,强调的是插入,数量变


事务的隔离级别(解药)       都是针对事务来说的
   *static int TRANSACTION_READ_COMMITTED
          指示不可以发生脏读的常量;不可重复读和虚读可以发生。
   *static int TRANSACTION_REPEATABLE_READ
          指示不可以发生脏读和不可重复读的常量;虚读可以发生。
    static int TRANSACTION_SERIALIZABLE
          指示不可以发生脏读、不可重复读和虚读的常量。     效率是最低的
      一般来说优先考虑前面两种,这种不怎么使用,因为这种在虚读的时候是在insert那个页面没有commit之前把所有数据的锁死的,
               这样子在web开发上是很不科学的,如果访问人数比较多,而进行操作的人操作又比较久,那么后面的人到底要到什么时候才能操作自己想要的数据啊

   总结:
    项目中,对于select操作不需要事务,对于其它操作(update/delete/insert)操作需要事务。

JDBC设置事务的隔离级别

package cn.itcast.web.jdbc.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import cn.itcast.web.jdbc.util.JdbcUtil;

//JDBC设置事务的隔离级别
public class Demo3 {
	//我(serializable)先执行
	public static void main(String[] args) {
		Connection conn = null;
		PreparedStatement pstmt = null;
		ResultSet rs = null;
		String sql = "select * from account";
		try {
			conn = JdbcUtil.getMySqlConnection();
			//设置事务的隔离级别
			conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
			conn.setAutoCommit(false);
			pstmt = conn.prepareStatement(sql);
			rs = pstmt.executeQuery();
			//休息
			Thread.sleep(20*1000);
			conn.commit();
		} catch (Exception e) {
			e.printStackTrace();
			try {
				conn.rollback();
				conn.commit();
			} catch (Exception e1) {
			}
		}finally{
			JdbcUtil.close(rs);
			JdbcUtil.close(pstmt);
			JdbcUtil.close(conn);
		}
	}
}


关于异常的处理(具体参照转账案例)
   1)关于分层结构中,处理异常的规则,参见<<关于异常的处理规则.JPG>>
   2)异常在项目中,往往替代boolean值,作为成功与否的标志
   除了service抛出自己包装的异常,其他都是抛出最原始的异常,而servlet则是用try语句捕获异常

事务,连接池,异常的使用_第1张图片

连接池
   1)传统方式找DriverManager要连接,数目是有限的。   也就是允许在线的人数是有很大的限制的,通过连接池可以连接无数个,而且速度还快
   2)传统方式的close(),并没有将Connection重用,只是切断应用程序和数据库的桥梁,即无发送到SQL命令到数据库端执行
   3)项目中,对于Connection不说,不会直接使用DriverManager取得,而使用连接池方式。
   4)DBCP和C3P0,都是Java开源的,都必须直接或间接实现javax.sql.DataSource接口
   5)DBCP连接池需要dbcp.properties文件,同时需加入3个对应的jar包
  *6)C3P0连接池需要在/WEB-INF/classes/目录下存放c3p0-config.xml文件,该类ComboPooledDataSource在创建时会自动在指定的目录下找xml文件,并加载默认设置,也可以不要配置文件,在代码中通过一系列的set语句来添加进去.
     hibernate默认使用的就是这个连接池
 

dbcp连接池:

package cn.itcast.web.jdbc.datasource;

import java.io.InputStream;
import java.sql.Connection;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.commons.dbcp.BasicDataSourceFactory;

//测试连接池DBCP的用法
public class Demo2 {
	public static void main(String[] args) throws Exception {
		long begin = System.currentTimeMillis();
		//加载属性文件
		InputStream is = Demo2.class.getClassLoader().getResourceAsStream("cn/itcast/web/jdbc/config/dbcp.properties");
		Properties props = new Properties();
		props.load(is);
		//创建DBCP连接池工厂
		BasicDataSourceFactory factory = new BasicDataSourceFactory(); 
		//创建数据源,即连接池
		DataSource ds = factory.createDataSource(props);
		for(int i=1;i<=50000;i++){
			//从连接池中取得一个空闲的连接对象
			Connection conn = ds.getConnection();
			if(conn!=null){
				System.out.println(i+":取得连接");
			}
			//将连接对象还回给连接池
			conn.close();
		}
		long end = System.currentTimeMillis();
		System.out.println("共用" + (end-begin)/1000+"秒");
	}
}
c3p0连接池:
package cn.itcast.web.jdbc.datasource;

import java.sql.Connection;
import com.mchange.v2.c3p0.ComboPooledDataSource;

//测试连接池C3P0的用法
public class Demo3 {
	public static void main(String[] args) throws Exception {
		long begin = System.currentTimeMillis();
		//创建C3P0连接池
		ComboPooledDataSource dataSource = new ComboPooledDataSource();
		for(int i=1;i<=100000;i++){
			Connection conn = dataSource.getConnection();
			if(conn!=null){
				System.out.println(i+":取得连接");
				conn.close();
			}
		}
		long end = System.currentTimeMillis();
		System.out.println("共用" + (end-begin)/1000+"秒");
	}
}

JNDI和在tomcat中配置DBCP连接池
  a)JNDI是Java命名和目录接口,不同的Web服务器有着不同的实现
  b)不同进程之间,可不同机器之间访问,叫远程访问     这种情况就使用JNDI,可以实现跨线程访问两一个线程的数据。。例如我开的这个线程肯定和tomcat连接池不是同一个线程,这样子就需要通过这个技术来连接到tomcat的线程从而获取connection
  c)JNDI和JDBC一样,都属于JavaEE规则之一
  d)基于tomcat如何配置DBCP连接池    /day15/src/cn/itcast/web/jdbc/web/JndiServlet.java
    >>修改tomcat/conf/context.xml文件    C:\Program Files\Apache Software Foundation\Tomcat 6.0\conf
        >>加入DB相关的jar包到tomcat/lib目录下     其实就是mysql的jar包
    >>重新启动tomcat服务器
  e)访问tomcat服务器的JNDI代码如下,是固定的:
    Context tomcatContext = (Context) context.lookup("java:comp/env");

package cn.itcast.web.jdbc.web;

import java.io.IOException;
import java.sql.Connection;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

//通过JNDI远程访问Tomcat服务器中的DBCP连接池
public class JndiServlet extends HttpServlet {
	public void doGet(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
		try {
			//创建具体Web服务器的JNDI对象
			Context context = new InitialContext();
			//远程查找Web服务器
			Context tomcatContext = (Context) context.lookup("java:comp/env");
			//在Web服务器内远程查找DBCP连接池服务
			//tomcatDS是在tomcat/conf/context.xml文件中配置的名字
			DataSource ds = (DataSource) tomcatContext.lookup("tomcatDS"); 
			//从DBCP连接池中取得一个空闲的连接
			Connection conn = ds.getConnection();
			//显示结果
			if(conn!=null){
				response.setContentType("text/html;charset=UTF-8");
				response.getWriter().write("取得连接");
				conn.close();
			}
		} catch (Exception e) {
			e.printStackTrace();
		} 
	}
}





你可能感兴趣的:(事务,连接池,异常的使用)