数据库的事务(超详细)

59 篇文章 0 订阅
12 篇文章 0 订阅

数据库的事务

一、定义

在 Java 中,事务管理是确保一组操作要么全部成功要么全部失败的一种机制。事务通常用于数据库操作,以确保数据的一致性和完整性。Java 提供了多种事务管理方式,包括编程式事务管理和声明式事务管理。

理解:MySQL:每一条语句都属于独立事务,默认自动管理提交的。

如果需要把多条语句当成一个整体,那么就需要把多条语句放在一个事务里面

二、事务的特性(ACID)

原子性( Atomicity )、一致性( Consistency )、隔离性( Isolation )和持久性( Durability )

原子性:事务是数据库的逻辑工作单位,事务中包含的各操作要么都完成,要么都不完成

一致性:事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。

隔离性:一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性:指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。

三、编程式事务管理

在编程式事务管理中,开发者显式地控制事务的开始、提交和回滚。这通常通过 JDBC(Java Database Connectivity)来实现。

开启事务:start transaction 提交事务:commit; 回滚事务:rollback

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

public class TransactionExample {
    public static void main(String[] args) {
        Connection conn = null;
        try {
            conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb", "user", "password");
            conn.setAutoCommit(false);  // 关闭自动提交

            // 执行 SQL 语句(需自己给数据库建表)
            conn.createStatement().executeUpdate("INSERT INTO bank (name, money) VALUES ('Alice', 1000)");
            conn.createStatement().executeUpdate("INSERT INTO bank (name, money) VALUES ('Bob', 1500)");

            conn.commit();  // 提交事务
        } catch (SQLException e) {
            if (conn != null) {
                try {
                    conn.rollback();  // 回滚事务
                } catch (SQLException ex) {
                    ex.printStackTrace();
                }
            }
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

四、事务的隔离级别

可以通过 @Transactional 注解的 isolation 属性设置事务的隔离级别,如 READ_COMMITTEDREPEATABLE_READ 等。

1、事务里可能出现的现象:

脏读:一个线程中的事务读到了另外一个线程中未提交的数据。

不可重复读:一个线程中的事务读到了另外一个线程中已经提交的update的数据。

虚读:一个线程中的事务读到了另外一个线程中已经提交的insert的数据。

2、4种隔离级别:

READ UNCOMMITTED(读未提交): 脏读、不可重复读、虚读有可能发生。

READ COMMITTED(读已提交): 避免脏读的发生,不可重复读、虚读有可能发生。

REPEATABLE READ(可重复读): 避免脏读、不可重复读的发生,虚读有可能发生。

SERIALIZABLE(串行化): 避免脏读、不可重复读、虚读的发生。

3、MySQL和ORACLE默认的隔离级别

MySQL:默认REPEATABLE READ

ORACLE:默认READ COMMITTED

4、SQL命令:查看当前的隔离级别

​ SELECT @@SESSION.transaction_isolation;

​ 设置当前的事务隔离级别

​ set transaction isolation level READ UNCOMMITTED

图片理解:
在这里插入图片描述

五、通过JDBC操作事务

1、需求:

需求:模拟银行用户转账

需求一:实现取钱操作。

需求二:实现存钱操作。

需求三:实现转账操作,要考虑多对用户,每对用户之间两两存取钱。

需求三的思想:每对用户实现两两存取钱,则这一对就共享同一个connection,但与其他对之间的connection是不一样的,相当于多线程之间的局部变量是不一样的,也就需要用到共享局部变量的思想,ThreadLocal来解决需求。

2、代码实现:

package com.qf.jdbc01;

import com.utils.DBUtil;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;

public class Test02 {
    public static void main(String[] args) {
        //传参
        try {
            saveMoney(1,200);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
        try {
            withdrawMoney(2,300);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    //场景一:取钱
   public static void withdrawMoney(int id,float money) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
       try {
           connection = DBUtil.getConnection();
           System.out.println(connection);
           String sql = "update bank set money = money-? where id = ?";
           statement = connection.prepareStatement(sql);
           statement.setFloat(1,money);
           statement.setInt(2,id);
           statement.executeUpdate();
       } finally {
           DBUtil.close(connection,statement,null);
       }
   }
   //场景二、存钱
    public static void saveMoney(int id,float money) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = DBUtil.getConnection();
            System.out.println(connection);
            String sql = "update bank set money=money+? where id=?";
            statement = connection.prepareStatement(sql);
            statement.setFloat(1,money);
            statement.setInt(2,id);
            statement.executeUpdate();

        } finally {
            DBUtil.close(connection,statement,null);
        }

    }
    //场景三:转账
//        try {
//
//            DBUtil.startTransaction();
//
//            withdrawMoney(1,200);
//
//            //System.out.println(10/0);
//
//            saveMoney(2,200);
//
//            DBUtil.commit();
//
//        } catch (Exception e) {
//            try {
//                DBUtil.rollback();
//            } catch (SQLException ex) {
//                throw new RuntimeException(ex);
//            }
//  }
//   }

 }


DButil代码实现:

package com.utils;

import java.io.IOException;
import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * 数据库工具类
 */
public class DBUtil {

    private static String url;
    private static String username;
    private static String password;

    private static final ThreadLocal<Object> local  ;//使用ThreadLocal处理线程安全的问题

    static{
        Properties properties = new Properties();
        try {
            properties.load(DBUtil.class.getClassLoader().getResourceAsStream("DBConfig.properties"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String driverName = properties.getProperty("driverName");
        url = properties.getProperty("url");
        username = properties.getProperty("username");
        password = properties.getProperty("password");

        try {
            Class.forName(driverName);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        local = new ThreadLocal<>();
    }

    /**
     * 获取连接对象
     */
    public static Connection getConnection() throws SQLException {
        Connection connection = DriverManager.getConnection(url,username,password);
        if (connection == null){
            connection = DriverManager.getConnection(url,username,password);
            local.set(connection);//将Connection对象添加到local中
        }
        return connection;
    }

    /**
     * 关闭资源
     */
    public static void close(Connection connection, Statement statement, ResultSet resultSet){
        if(resultSet != null){
            try {
                resultSet.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(statement != null){
            try {
                statement.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        if(connection != null){
            try {
                if (connection.getAutoCommit()){
                    connection.close();
                    if (connection == null) {
                        connection.close();
                        local.set(null);
                    }
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
    /**
     * 开启事务
     */
    public static void startTransaction() throws SQLException {
        Connection connection = getConnection();
        connection.setAutoCommit(false);
    }
    /**
     * 提交事务
     */
    public static void commit() throws SQLException {
        Connection connection = (Connection) local.get();
        if(connection != null){
            connection.commit();
            local.set(null);
        }
    }
    /**
     * 事务回滚
     */
    public static void rollback() throws SQLException {
        Connection connection = (Connection) local.get();
        if (connection != null){
            connection.rollback();
            local.set(null);
        }
    }

    /**
     * 更新数据(添加、删除、修改)
     */
    public static int commonUpdate(String sql,Object... params) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            paramHandler(statement,params);
            int num = statement.executeUpdate();
            return num;
        }finally {
            close(connection,statement,null);
        }
    }

    /**
     * 添加数据 - 主键回填(主键是int类型可以返回)
     */
    public static int commonInsert(String sql,Object... params) throws SQLException {
        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;
        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
            paramHandler(statement,params);
            statement.executeUpdate();

            resultSet = statement.getGeneratedKeys();
            int primaryKey = 0;
            if(resultSet.next()){
                primaryKey = resultSet.getInt(1);
            }
            return primaryKey;
        }finally {
            close(connection,statement,resultSet);
        }
    }

    /**
     * 查询多个数据
     */
    public static <T> List<T> commonQueryList(Class<T> clazz,String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException {

        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            paramHandler(statement,params);
            resultSet = statement.executeQuery();

            //获取表数据对象
            ResultSetMetaData metaData = resultSet.getMetaData();
            //获取字段个数
            int count = metaData.getColumnCount();

            List<T> list = new ArrayList<>();

            while(resultSet.next()){

                T t = clazz.newInstance();

                //获取字段名及数据
                for (int i = 1; i <= count; i++) {
                    String fieldName = metaData.getColumnName(i);
                    Object fieldVal = resultSet.getObject(fieldName);
                    setField(t,fieldName,fieldVal);
                }
                list.add(t);
            }
            return list;
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
    }

    /**
     * 查询单个数据
     */
    public static <T> T commonQueryObj(Class<T> clazz,String sql, Object... params) throws SQLException, InstantiationException, IllegalAccessException {

        Connection connection = null;
        PreparedStatement statement = null;
        ResultSet resultSet = null;

        try {
            connection = getConnection();
            statement = connection.prepareStatement(sql);
            paramHandler(statement,params);
            resultSet = statement.executeQuery();

            //获取表数据对象
            ResultSetMetaData metaData = resultSet.getMetaData();
            //获取字段个数
            int count = metaData.getColumnCount();

            if(resultSet.next()){

                T t = clazz.newInstance();

                //获取字段名及数据
                for (int i = 1; i <= count; i++) {
                    String fieldName = metaData.getColumnName(i);
                    Object fieldVal = resultSet.getObject(fieldName);
                    setField(t,fieldName,fieldVal);
                }
                return t;
            }
        } finally {
            DBUtil.close(connection,statement,resultSet);
        }
        return null;
    }

    /**
     * 处理statement对象参数数据的处理器
     */
    private static void paramHandler(PreparedStatement statement,Object... params) throws SQLException {
        for (int i = 0; i < params.length; i++) {
            statement.setObject(i+1,params[i]);
        }
    }

    /**
     * 获取当前类及其父类的属性对象
     * @param clazz class对象
     * @param name 属性名
     * @return 属性对象
     */
    private static Field getField(Class<?> clazz,String name){

        for(Class<?> c = clazz;c != null;c = c.getSuperclass()){
            try {
                Field field = c.getDeclaredField(name);
                return field;
            } catch (NoSuchFieldException e) {
            } catch (SecurityException e) {
            }
        }
        return null;
    }

    /**
     * 设置对象中的属性
     * @param obj 对象
     * @param name 属性名
     * @param value 属性值
     */
    private static void setField(Object obj,String name,Object value){

        Field field = getField(obj.getClass(), name);
        if(field != null){
            field.setAccessible(true);
            try {
                field.set(obj, value);
            } catch (IllegalArgumentException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值