事务的特性:
原子性
(atomicity):事务是数据库的逻辑工作单位,而且是必须是原子工作单位,对于其数据修改,要么全部执行,要么全部不执行。一致性
(consistency):事务在完成时,必须是所有的数据都保持一致状态。在相关数据库中,所有规则都必须应用于事务的修改,以保持所有数据的完整性。即使此时再执行回滚操作也不能撤消所做的更改
事务(Transaction)
:是并发控制的单元,是用户定义的一个操作序列。这些操作要么都做,要么都不做,是一个不可分割的工作单位。通过事务,能将逻辑相关的一组操作绑定在一起,以便服务器 保持数据的完整性。事务通常是以begin transaction
开始,以commit
或rollback
结束。Commint
表示提交,即提交事务的所有操作。具体地说就是将事务中所有对数据的更新写回到磁盘上的物理数据库中去,事务正常结束。Rollback
表示回滚,即在事务运行的过程中发生了某种故障,事务不能继续进行,系统将事务中对数据库的所有已完成的操作全部撤消,滚回到事务开始的状态。
自动提交事务
:每条单独的语句都是一个事务。每个语句后都隐含一个commit
(默认)显式事务
:以begin transaction
显示开始,以commit
或rollback
结束。隐式事务
:当连接以隐式事务模式进行操作时,数据库引擎实例将在提交或回滚当前事务后自动启动新事务。无须描述事物的开始,只需提交或回滚每个事务。但每个事务仍以commit
或rollback
显式结束。alter table,insert,create,open ,delete,revoke ,drop,select, fetch ,truncate table,grant,update
在发出commit
或rollback
;语句之前,该事务将一直保持有效。在第一个事务被提交或回滚之后,下次当连接执行以上任何语句时,数据库引擎实例都将自动启动一个新事务。该实例将不断地生成隐性事务链,直到隐性事务模式关闭为止。并发问题可归纳为以下几类:
丢失更新
:撤销一个事务时,把其他事务已提交的更新数据覆盖(A和B事务并发执行,A事务执行更新后,提交;B事务在A事务更新后,B事务结束前也做了对该行数据的更新操作,然后回滚,则两次更新操作都丢失了)脏读
:一个事务读到另一个事务未提交的更新数据(A和B事务并发执行,B事务执行更新后,A事务查询B事务没有提交的数据,B事务回滚,则A事务得到的数据不是数据库中的真实数据。也就是脏数据,即和数据库中不一致的数据)不可重复读
:一个事务读到另一个事务已提交的更新数据(A和B事务并发执行,A事务查询数据,然后B事务更新该数据,A再次查询该数据时,发现该数据变化了)覆盖更新
:这是不可重复读中的特例,一个事务覆盖另一个事务已提交的更新数据(即A事务更新数据,然后B事务更新该数据,A事务查询发现自己更新的数据变了)虚读(幻读)
:一个事务读到另一个事务已提交的新插入的数据(A和B事务并发执行,A事务查询数据,B事务插入或者删除数据,A事务再次查询发现结果集中有以前没有的数据或者以前有的数据消失了)数据库系统提供了四种事务隔离级别供用户选择:
ISOLATION_DEFAULT
(默认隔离级别 ):这是一个PlatfromTransactionManager
默认的隔离级别,使用数据库默认的事务隔离级别,oracle
默认的是:READ_COMMITTED
,mysql
默认的是:REPEATABLE_READ
Read Uncommitted
(读未提交数据):一个事务在执行过程中可以看到其他事务没有提交的新插入的记录,而且能看到其他事务没有提交的对已有记录的更新Read Commited
(读已提交数据):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,而且能看到其他事务已经提交的对已有记录的更新Repeatable Read
(可重复读):一个事务在执行过程中可以看到其他事务已经提交的新插入的记录,但是不能看到其他其他事务对已有记录的更新Serializable
(串行化):一个事务在执行过程中完全看不到其他事务对数据库所做的更新(事务执行的时候不允许别的事务并发执行。事务串行化执行,事务只能一个接着一个地执行,而不能并发执行)丢失更新 | 脏读 | 非重复读 | 覆盖更新 | 幻像读 | |
---|---|---|---|---|---|
未提交读 | Y | Y | Y | Y | Y |
已提交读 | N | N | Y | Y | Y |
可重复读 | N | N | N | N | Y |
串行化 | N | N | N | N | N |
保存点(SavePoint
),JDBC定义了SavePoint
接口,提供在一个更细粒度的事务控制机制。当设置了一个保存点后,可以rollback
到该保存点处的状态,而不是rollback
整个事务。
Connection
接口的setSavepoint
和releaseSavepoint
方法可以设置和释放保存点。
JDBC规范虽然定义了事务的以上支持行为,但是各个JDBC驱动,数据库厂商对事务的支持程度可能各不相同。如果在程序中任意设置,可能得不到想要的效果。为此,JDBC
提供了DatabaseMetaData
接口,提供了一系列JDBC特性支持情况的获取方法。比如,通过DatabaseMetaData.supportsTransactionIsolationLevel
方法可以判断对事务隔离级别的支持情况,通过DatabaseMetaData.supportsSavepoints
方法可以判断对保存点的支持情况
rollback
和 commit
的作用都完成对数据库的一次操作,并且释放当前的一些资源, 对于commit之后的事务,是不能够再进行回滚
,但是对于回滚之后的失误,在不同的情况下却可以选择提交,rollback()
和rollback(Savepoint)
的区别也就在于此
这两方面方法会抛出SQLException,如果该事务为自动提交,即:
connection.setAutoCommint(true)
,或者是默认设置.
参看以下代码:
private String sql = "insert into t_transaction_test (name,value) values(?,?)"; pstm = conn.prepareStatement(sql); pstm.setString(1, "test9"); pstm.setString(2, "test9"); pstm.execute(); Savepoint sp = conn.setSavepoint(); pstm.setString(1, "test10"); pstm.setString(2, "test10"); pstm.execute(); Savepoint sp2 = conn.setSavepoint(); conn.rollback(); conn.commit(); System.out.println("Insert OK " + sp.getSavepointId());
在这段代码中,程序已经被rollback
,因此接下来的commit()
是没有任何意义的,但是如果将程序改为:
conn.rollback(); conn.commit(sp);
那么接下来的commit()
将是有意义的,数据库中会记录第一条数据
当需要向数据库发送一批SQL
语句执行时,应避免向数据库一条条的发送执行,而应采用JDBC
的批处理机制,以提升执行效率
使用Statement批处理
Statement.addBatch(sql) list 执行批处理SQL语句
executeBatch()方法:执行批处理命令
clearBatch()方法:清除批处理命令
Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JDBCManager.getConnection(); String sql1 = "insert into user(name,password,email,birthday) values('kkk','123','abc@sina.com','1978-08-08')"; String sql2 = "update user set pw='123456' where id=3"; st = conn.createStatement(); st.addBatch(sql1); //把SQL语句加入到批命令中 st.addBatch(sql2); //把SQL语句加入到批命令中 st.executeBatch(); } finally{ JDBCManager.DBclose(con,st,rs); }
采用Statement.addBatch(sql)
方式实现批处理:
优点:可以向数据库发送多条不同的SQL
语句。
缺点:SQL语句没有预编译。
当向数据库发送多条语句相同,但仅参数不同的SQL语句时,需重复写上很多条SQL语句。例如:
Insert into user(name,password) values(‘aa’,’111’);
Insert into user(name,password) values(‘bb’,’222’);
Insert into user(name,password) values(‘cc’,’333’);
Insert into user(name,password) values(‘dd’,’444’);
PreparedStatement批处理
PreparedStatement.addBatch();
conn = JDBCManager.getConnection();//获取工具; String sql = "insert into user(name,password,email,birthday) values(?,?,?,?)"; st = conn.prepareStatement(sql);//预处理sql语句; for(int i=0;i<50000;i++){ st.setString(1, "aaa" + i); st.setString(2, "123" + i); st.setString(3, "aaa" + i + "@sina.com"); st.setDate(4,new Date(1980, 10, 10)); st.addBatch();//将一组参数添加到此 PreparedStatement 对象的批处理命令中。 if(i%1000==0){ st.executeBatch(); st.clearBatch();清空此 Statement 对象的当前 SQL 命令列表。 } }
st.executeBatch();
将一批命令提交给数据库来执行,如果全部命令执行成功,则返回更新计数组成的数组。返回数组的 int
元素的排序对应于批中的命令,批中的命令根据被添加到批中的顺序排序
采用PreparedStatement.addBatch()
实现批处理
优点:发送的是预编译后的SQL
语句,执行效率高。
缺点:只能应用在SQL语句相同,但参数不同的批处理中。因此此种形式的批处理经常用于在同一个表中批量插入数据,或批量更新表的数据。
Connection con=null; PreparedStatement ps=null; con = JDBCManager.getConnection(); String sql="insert into users(name,password) values(?,?)"; try { ps = con.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);//获取返回的主键; ps.setString(1, "qq"); ps.setString(2, "123"); ps.executeUpdate(); ResultSet rs=ps.getGeneratedKeys();//返回一个结果集,保存着产生的key的结果集, while(rs.next()){ System.out.println(rs.getObject(1));//结果集只有一个值; } } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ JDBCManager.DBclose(con, ps, null); }
点击了解MySQL存储过程创建
点击了解Oracle存储过程创建
JDBC调用存数过程(创建好存储过程体):
当值是输入函数时:
Connection con=null; CallableStatement cs=null; con=JDBCManager.getConnection(); try { cs=con.prepareCall("{call pd(?,?)}");//存储过程语句; cs.setString(1, "yy"); cs.setString(2, "msn"); cs.execute();//执行 System.out.println("执行成功"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ JDBCManager.DBclose(con, cs, null); }
当输入和输出同时:
Connection con=null; CallableStatement cs=null; con=JDBCManager.getConnection(); try { cs=con.prepareCall("{call pcall(?,?)}"); cs.setInt(1, 10); cs.registerOutParameter(2,Types.CHAR);//获取一下注册类型; cs.execute();//执行 System.out.println(cs.getString(2));//获取第二个String类型的参数值; cs.execute(); System.out.println("执行成功"); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ JDBCManager.DBclose(con, cs, null); }
Oracle
和MySQL
的数据类型
数据类型 | Oracle | MySQL |
---|---|---|
NUMBER(p,s) | 存储数值数据类型,如浮点型、整数型、分数、双精度等。其中p为精度,表示数字的总位数(1 <= p <=38) ; s为范围,表示小数点右边的位数,它在-84至127之间。默认38位 | |
TINYINT | 微整型,1字节 | |
SMALLINT | 小整型,2字节 | |
MEDIUMINT | 中整型,3字节 | |
INT或INTEGER | 整数数据类型 | 整型,4字节 |
BIGINT | 大整型,8字节 | |
FLOAT | 浮点数数据类型 | 单精度浮点数值,4字节 |
DOUBLE | 双精度浮点数值,8字节 | |
DECIMAL | 对DECIMAL(M,D) ,如果M>D,为M+2否则为D+2 | |
CHAR(n) | n=1to2000字节,定长字符串,n字节长,如果不指定长度,缺省为1个字节长(一个汉字为2字节) | 定长字符串,0-255字节 |
NCHAR(n) | NLS(national language support , 国际语言支持)的数据类型仅可以存储由数据库 NLS 字符集定义的 Unicode 字符集。Oracle最多可以存储2000Byte | NLS(national language support , 国际语言支持)的数据类型仅可以存储由数据库 NLS 字符集定义的 Unicode 字符集。 |
VARCHAR(n) | 最多可以以可变长度来存储4000B,因此不需要空格来作补充 | 变长字符串,0-255字节 |
NVARCHAR(n) | n=1to4000字节,最多可以以可变长度来存储4000B,因此不需要空格来作补充 | NLS(national language support , 国际语言支持)的数据类型仅可以存储由数据库 NLS 字符集定义的 Unicode 字符集 |
VARCHAR2(n) | VARCHAR2 比 VARCHAR 更适合使用,由于兼容性的原因,所以仍然在 Oracle 数据库中保留着 VARCHAR | |
NVARCHAR2 | NLS 的数据类型与 VARCHAR2 数据类型等价。这个数据类型最多可存储4000B | |
CLOB | 存储大量的单字节字符数据和多字节字符数据。存储的最大容量为4G | |
NCLOB | 存储可变长度的Unicode字符集字符数据,存储的最大容量为4G | |
BLOB | 存储较大的二进制对象。例:图形,视频,音频等 | 二进制形式的长文本数据,0-65 535字节 |
BFILE | bfile:文件定位器;指向位于服务器文件系统是二进制文件(存储一个文件路径) | |
LONG | 存储可变长度的字符数据,最多存储2GB | |
RAW | 存储基于字节的数据。最多存储2000个字节,使用需指定大小。raw数据类型可以建立索引 | |
LONG RAW | 存储可变长度的二进制数据。最多能存储2GB。不能索引 | |
TINYBLOB | 不超过 255 个字符的二进制字符串,0-255字节 | |
TINYTEXT | 短文本字符串,0-255字节 | |
TEXT | 长文本数据,0-65 535字节 | |
MEDIUMBLOB | 二进制形式的中等长度文本数据 | |
MEDIUMTEXT | 中等长度文本数据,0-16 777 215字节 | |
LOGNGBLOB | 二进制形式的极大文本数据,0-4 294 967 295字节 | |
LONGTEXT | 极大文本数据,0-4 294 967 295字节 | |
DATE | 存储表的日期和时间数据,使用7个字节固定长度,每个字节分别存储世纪,年,月,日,小时,分和秒;值从公元前4712年1月1日到公元9999年12月31日。 Oracle中的sysdate函数功能是返回当前的日期和时间 | 日期值,YYYY-MM-DD |
TIMESTAMP | 存储日期的年,月,日以及时间的小时,分和秒值。其中秒值精确到小数点后6位,同时包含时区信息。 Oracle中的systimestamp函数功能是返回当前日期,时间和时区 | |
TIME | 时间值或持续时间,HH:MM:SS | |
YEAR | 年份值,YYYY | |
DATETIME | 混合日期和时间,YYYY-MM-DD HH:MM:SS | |
TIMESTAMP | 混合日期和时间值,时间戳 | 混合日期和时间值,时间戳,YYYYMMDD HHMMSS |
ENUM | 一个 ENUM 类型最多可以包含 65536 个元素,其中一个元素被 MySQL 保留,用来存储错误信息,这个错误值用索引 0 或者一个空字符串表示 | |
SET | 容器类型,一个 SET 类型最多可以包含 64 项元素,且不可能包含两个相同的元素 |