事务的概念
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显示操作事务的APIpublic 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 ThreadLocaltl = new ThreadLocal (); //局部线程变量 //静态块:加载文件 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)传统方式找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(); } }}