一、事务的隔离级别
多个事务在数据库中同时运行,当他们访问到数据库中的同一个数据的时候,如果没有采用隔离机制,就会导致一些并发问题的发生,在数据库中有以下几个并发问题
①读"脏"数据 指事务T1修改了某一数据后,并未提交,而此时事务T2又读到了这个数据(此时T2读到的是T1修改后未提交的数据),然后又因为某种原因导致T1被撤销(即rollback),这时T2读到的数据仍然是T1回滚之前修改的未提交的数据,因此称T2读道德数据为"脏"数据
②幻影现象 事务T1按一定的条件读取数据库中的某些数据记录,而事务T2删除了其中的部分内容或又插入了一些记录,当T1再次去读取该数据时,发现某些记录消失了或者多了一些记录,这种现象被称为幻影现象
③不可重复读 指的是事务T1读取数据后,事务T2对该数据进行了修改,,使得T1下一次再次读取该数据时与前一次所读到的数据不一致。这就被称之为不可重复读
为了解决这些问题,数据库中规定了多种事务隔离级别,不同的隔离级别的抗干扰程度不同,隔离级别越高,数据的一致性就越好,但相应的数据库的性能就要差一些,因此应该根据具体的情况来选择隔离级别。在MySQL中有四种隔离级别,分别是以下四种:
①读未提交数据 READ UNCOMMITTED 允许事务读取未被其他事务提交的数据,上面三种并发问题都会出现
②读已提交数据 READ COMMITED 只允许事务读取已经被其他事务提交的数据,可以避免读"脏数据",但其他两种问题还是会出现
③可重复读 REPEATABLE READ 确保一个事务可以多次读取一个相同的数据,在该事务运行期间,其他事务精致访问该数据,可以避免读"脏"数据和不可重复读
④串行化 SERIALIZABLE 确保事务可以从一个表中读取相同的行,在这个事务运行期间,禁止其他事务对该表进行修改,所有并发问题都可避免,但数据库的性能比较差
此外,在MySQL中默认的事务隔离级别为REPEATABLE READ,而Oracle只支持两种事务隔离级别,分别是READ COMMITED与SERIALIZABLE,其默认事务隔离级别为READ COMMITED
所以,我们在利用JDBC对数据库执行操作的时候,想要避免并发问题,可以在建立了数据库连接利用Conneciton对象中的setTransactionIsolation()方法设置隔离级别,该方法的参数为一个常量,分别是TRANSACTION_READ_UNCOMMITTED、TRANSACTION_READ_COMMITTED、TRANSACTION_REPEATABLE_READ和TRANSACTION_SERIALIZABLE他们分别对应前面说的隔离级别。
下面是一个具体的事例来测试事务的隔离级别:
现在本地数据库中有这样一个表:
下面进行测试
//测试类 用来测试事务的隔离级别的类
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.junit.Test;
public class TestTransaction {//事务的多个操作
DAO dao = new DAO();
@Test
public void testTransactionIsolation(){//事务一 执行更新操作
Connection con = null;
try {
con = JDBCTools.getConnection();
con.setAutoCommit(false);
String sql = "UPDATE customer SET money=money-500 WHERE id=1";
dao.update(con, sql);
con.commit();//在测试时在此处加个断点,debug时停在此处,然后去运行事务二,即可看到结果
} catch (Exception e) {
e.printStackTrace();
}finally{
dao.release(null, con, null);
}
}
@Test
public void testTransactionIsolation1(){//事务二 查询操作
Connection con = null;
try {
con = JDBCTools.getConnection();
String sql = "SELECT money FROM customer WHERE id=1";
Integer value = getvalues(con, sql);
System.out.println(value);
} catch (Exception e) {
e.printStackTrace();
}finally{
dao.release(null, con, null);
}
}
/**
* 返回某条记录的某一字段的值
* @param sql
* @param args
* @return
*/
public <E> E getvalues(Connection con,String sql,Object ... args){
PreparedStatement ps = null;
ResultSet rs = null;//所得结果集只为一行一列
try {
con.setTransactionIsolation(con.TRANSACTION_READ_COMMITTED);//设置隔离级别为读已提交的
ps = con.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i+1,args[i]);
}
rs = ps.executeQuery();
if(rs.next()){
return (E)rs.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCTools.release(rs, null, ps);
}
return null;
}
}
//DAO类 包含了对数据库执行一系列操作的方法
import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.beanutils.BeanUtils;
public class DAO {
/**
* 数据库的更新
* @param sql
* @param args
*/
public void update(Connection con,String sql,Object ... args){//数据库的更新操作,可实现增、删、改的操作
PreparedStatement ps = null;
try {
ps = con.prepareStatement(sql);
for(int i = 0;i<args.length;i++){//填充占位符
ps.setObject(i + 1, args[i]);
}
ps.executeUpdate();
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCTools.release(null, null, ps);
}
}
/**
* 返回某条记录的某一字段的值
* @param sql
* @param args
* @return
*/
public <E> E getvalues(Connection con,String sql,Object ... args){
PreparedStatement ps = null;
ResultSet rs = null;//所得结果集只为一行一列
try {
ps = con.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i+1,args[i]);
}
rs = ps.executeQuery();
if(rs.next()){
return (E)rs.getObject(1);
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCTools.release(rs, null, ps);
}
return null;
}
/**
* 数据库的单个查询操作
* @param clazz
* @param sql
* @param args
* @return
*/
public <T> T getForOne(Connection con,Class<T> clazz,String sql,Object ... args){//数据库的查询操作
T entity = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement(sql);
for(int i = 0;i < args.length;i++){//填充占位符
ps.setObject(i + 1,args[i]);
}
rs = ps.executeQuery();//得到结果集,利用它可以知道某一行中某一列的具体值
if(rs.next()){
ResultSetMetaData rsmd = rs.getMetaData();//该对象可以知道结果集有几列,以及每一列所对应的别名等信息
Map<String,Object> valueMap = new HashMap<>();//建立Map集合用来存结果集中的列名以及对应的属性值
for(int i = 0;i < rsmd.getColumnCount();i++){
String ColumnLabel = rsmd.getColumnLabel(i + 1);//得到查询出的每一列的列名
Object ColumnValue = rs.getObject(i + 1);//得到每一列所对应的值
valueMap.put(ColumnLabel, ColumnValue);
}
if(valueMap.size() > 0){
entity = clazz.newInstance();
for(Map.Entry<String, Object> entry : valueMap.entrySet()){//利用反射为对应的属性赋值
String fieldName = entry.getKey();
Object fieldvalue = entry.getValue();
// Field f1 = clazz.getDeclaredField(fieldName);
// f1.setAccessible(true);
// f1.set(entity, fieldvalue);
BeanUtils.setProperty(entity, fieldName, fieldvalue);//对Java类属性赋值
}
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCTools.release(rs, null, ps);
}
return entity;
}
/**
* 数据库的多个查询返回结果集合的操作
* @param clazz
* @param sql
* @param args
* @return
*/
public <T> List<T> getForList(Connection con,Class<T> clazz,String sql,Object ... args){
List<T> list = new ArrayList<>();
PreparedStatement ps = null;
ResultSet rs = null;
try {
ps = con.prepareStatement(sql);
for(int i = 0;i < args.length;i++){
ps.setObject(i + 1, args[i]);
}
rs = ps.executeQuery();//得到结果集
List<Map<String,Object>> valueList = new ArrayList<>();//用于存放多条记录的List集合
ResultSetMetaData rsmd = rs.getMetaData();
Map<String,Object> map = null;//存放一条记录的Map集合
while(rs.next()){//处理结果集
map = new HashMap<String,Object>();
for(int i = 0;i < rsmd.getColumnCount();i++){
String columLabel = rsmd.getColumnLabel(i + 1);
Object value = rs.getObject(i + 1);
//将一条记录存入mao集合中
map.put(columLabel, value);
}
valueList.add(map);
}
//判断valueList是否为空 若不为空,则遍历valueList集合,得到一个个Map对象,将其转为Class参数对应的对象
T bean = null;
if(valueList.size() > 0){
for(Map<String,Object> each : valueList){
for(Map.Entry<String, Object> e : each.entrySet()){
String fieldname = e.getKey();
Object fieldvalue = e.getValue();
//为对应的Java类属性赋值
bean = clazz.newInstance();
BeanUtils.setProperty(bean, fieldname, fieldvalue);
}
//将T对象放入list中
list.add(bean);
}
}
} catch (Exception e) {
e.printStackTrace();
}finally{
JDBCTools.release(rs, null, ps);
}
return list;
}
}
//JDBC工具类 包含数据库的连接,更新,关闭等功能
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 class JDBCTools {
public static Connection getConnection() throws Exception{//连接数据库
String driverClass = null;
String url = null;
String user = null;
String password = null;
Properties properties = new Properties();
InputStream in = Review.class.getClassLoader().getResourceAsStream("jdbc.properties");
properties.load(in);
driverClass = properties.getProperty("driver");
url = properties.getProperty("jdbcurl");
user = properties.getProperty("user");
password = properties.getProperty("password");
Class.forName(driverClass);
return DriverManager.getConnection(url, user, password);
}
public static void release(Connection con , Statement state){//关闭数据库连接
if(state != null){
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(con != null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
public static void release(ResultSet rs , Connection con , Statement state){//关闭数据库连接
if(rs != null)
{
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(state != null){
try {
state.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(con != null){
try {
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
最后的运行结果为