事务的概念
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)传统方式找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(); } } }