细致刨析JDBC ③ 高级篇

news2024/11/25 19:00:09

目录

一、JDBC优化及工具类封装

1.现有问题

2.JDBC工具类封装V1.0

3.ThreadLocal

4.JDBC工具类封装V2.0

二、DAO封装及BaseDAO工具类

1.BaseDAO概念

2.BaseDao层代码实现

① BaseDao层——通用的修改方法

② 通用的查询方法

③ 单行查询方法优化

三、事务

1、事务回顾

2.JDBC中事务的实现

关键代码

JDBC事务代码实现


别问、别说、别停留,去做

                                —— 24.9.11

本文中用到的反射相关知识点请看博主的另一博文:http://t.csdnimg.cn/2YVxD

一、JDBC优化及工具类封装

1.现有问题

我们在使用JDBC的过程中,发现部分代码存在冗余的问题

① 创建连接池。

        没有必要让每个用户都独享一个连接池,而是要将连接池设置成一个整个项目中都可以共享的连接池

② 获取连接。

③ 连接的回收。

2.JDBC工具类封装V1.0

方法类

package JDBC3_Senior.util;

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/*
    JDBC工具类(V1.0)
        1.维护一个连接池对象,该连接池在整个项目内都能使用
        2.对外提供在连接池中获取链接的方法
        3.对外提供回收链接的方法
    注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法
 */
public class JDBC_util {
    // 创建连接池引用,因为要提供当前项目的全局使用,所以创建成静态的
    // 写成接口类型在更换连接池时,只需要更换实现而不用更换引用
    private static DataSource ds;

    // 在项目启动时,即创建连接池对象,赋值给ds
    // 静态代码块
    static{
        try {
            // 创建一个properties集合
            Properties prop = new Properties();
            // 创建一个类加载器,拿到一个字节输入流
            InputStream input = JDBC_util.class.getClassLoader().getResourceAsStream("db.properties");
            prop.load(input);
            // 构建一个连接池对象
            ds = DruidDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 方法1:对外提供在连接池中获取链接的方法
    // 维护连接池对象
    public static Connection getConnection() {
        try {
            return ds.getConnection();
        }catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    // 方法2:对外提供回收链接的方法
    // 回收链接
    public static void release(Connection conn) {
        try {
            // 关闭链接对象
            conn.close();
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

}

测试类 

import org.junit.Test;

import java.sql.Connection;

public class JDBCUtilTest {

    @Test
    public void testGetConnection() {
        Connection connection = JDBC_util.getConnection();
        System.out.println(connection);
        // CRUD

        JDBC_util.release(connection);
    }
}

3.ThreadLocal

        JDK 1.2的版本中就提供java.lang;.ThreadLocal,为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。通常用来在在多线程中管理共享数据库连接、Session等。

        ThreadLocal用于保存某个线程共享变量,原因是在Java中,每一个线程对象中都有一个ThreadLocalMap<ThreadLocal, Object>,其key就是一个ThreadLocal,而Object即为该线程的共享变量。

        而这个map是通过ThreadLocal的set和get方法操作的。对于同一个static ThreadLocal,不同线程只能从中get、set、remove自己的变量,而不会影响其他线程的变量。

                ① 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
                ② 线程间数据隔离。
                ③ 进行事务操作,用于存储线程事务信息。
                ④ 数据库连接, Session会话管理

1、ThreadLocal对象.get:获取ThreadLocal中当前线程共享变量的值,

2、ThreadLocal对象.set: 设置ThreadLocal中当前线程共享变量的值

3、ThreadLocal对象.remove:移除ThreadLocal中当前线程共享变量的值

4.JDBC工具类封装V2.0

方法类

import com.alibaba.druid.pool.DruidDataSourceFactory;

import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;

/*
    JDBC工具类(V1.0)
        1.维护一个连接池对象,该连接池在整个项目内都能使用,同时维护了一个线程绑定变量的ThreadLocal对象
        2.对外提供在ThreadLocal中获取链接的方法
        3.对外提供回收链接的方法,回收过程中,将要回收的链接从ThreadLocal之中移除
    注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法
    注意:使用ThreadLocal就是为了一个线程在多次数据库操作过程中,使用的是同一个链接!
 */
public class JDBC_utilV2 {
    // 创建连接池引用,因为要提供当前项目的全局使用,所以创建成静态的
    // 写成接口类型在更换连接池时,只需要更换实现而不用更换引用
    private static DataSource ds;
    // 创建一个静态的ThreadLocal对象
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    // 方法1:维护一个连接池,在项目启动时,即创建连接池对象,赋值给ds
    // 静态代码块
    static{
        try {
            // 创建一个properties集合
            Properties prop = new Properties();
            // 创建一个类加载器,拿到一个字节输入流
            InputStream input = JDBC_util.class.getClassLoader().getResourceAsStream("db.properties");
            prop.load(input);
            // 构建一个连接池对象
            ds = DruidDataSourceFactory.createDataSource(prop);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    // 方法2:对外提供在连接池中获取链接的方法
    // 维护连接池对象
    public static Connection getConnection() {
        try {
            // 在ThreadLocal中获取Connection
            Connection connection = threadLocal.get();
            // 如果threadLocal里没有存储Connection,这就是第一次获取
            if (connection == null) {
                // 在连接池中获取一个对象,存储在连接池ThreadLocal里
                connection = ds.getConnection();
                threadLocal.set(connection);
            }
            return connection;
        }catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    // 方法3:对外提供回收链接的方法
    // 回收链接
    public static void release() {
        try {
            // 关闭链接对象
            Connection connection = threadLocal.get();
            if (connection != null) {
                // 从threadLocal中移除当前已经存储的Connection对象
                threadLocal.remove();
                // 将Connection对象还给连接池
                connection.close();
            }
        }catch (SQLException e){
            throw new RuntimeException(e);
        }
    }

}

 测试类

    @Test
    public void testJDBCV2(){
        Connection con1 = JDBC_util.getConnection();
        Connection con2 = JDBC_util.getConnection();
        Connection con3 = JDBC_util.getConnection();

        System.out.println(con1);
        System.out.println(con2);
        System.out.println(con3);

        Connection connection1 = JDBC_utilV2.getConnection();
        Connection connection2 = JDBC_utilV2.getConnection();
        Connection connection3 = JDBC_utilV2.getConnection();

        System.out.println(connection1);
        System.out.println(connection2);
        System.out.println(connection3);
    }

二、DAO封装及BaseDAO工具类

DAO:Data Access 0bject,数据访问对象。

Java是面向对象语言,数据在Java中通常以对象的形式存在。一张表对应一个实体类,一张表的操作对应一个DAO对象

在Java操作数据库时,我们会将对同一张表的增删改查操作统一维护起来,维护的这个类就是DAO层。

DAO层只关注对数据库的操作,供业务层Service调用,将职责划分清楚

1.BaseDAO概念

基本上每一个数据表都应该有一个对应的DAO接口及其实现类,发现对所有表的操作(增、删、改、查)代码重复度很高,所以可以抽取公共代码,给这些DAO的实现类可以抽取一个公共的父类,复用增删改查的基本操作,我们称为BaseDAO。

2.BaseDao层代码实现

通用的增删改的方法。

        @param sql 调用者要执行的SQL语句

        @param params SQL语句中的占位符要赋值的参数

        @return 受影响的行数

① BaseDao层——通用的修改方法

    // 通用的修改方法  数组中的可变长参数
    public int executeUpdate(String sql,Object...params) throws Exception{
        // 1.通过JDBCUtilV2获取数据库链接
        Connection connection = JDBC_utilV2.getConnection();

        // 2.预编译SQL语句,sql语句由外部传入
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        // 3.为占位符赋值,执行SQL,接收返回结果
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                // 循环操作给占位符赋值
                // 占位符是从1开始的,参数数组下标从0开始
                // 用setObject为类型进行赋值,object通用类型
                // 参数顺序不同
                preparedStatement.setObject(i+1,params[i]);
            }
        }
        // 拿到受影响的行数
        int i = preparedStatement.executeUpdate();

        // 4.释放资源
        preparedStatement.close();
        // 如果事务开启
        if (connection.getAutoCommit()){
            JDBC_utilV2.release();
        }
        // 5.返回结果
        return i;
    }

② 通用的查询方法

   /*
        通用的查询:多行多列、单行多列、单行单列
            多行多列:List<Employee>
            单行多列:Employee
            单行单列:封装的是一个结果:Double、Integer、......
         封装过程:
            1.返回的类型:泛型:类型不确定,调用者知道,调用时,将此次查询的结果类型告知BaseDAO就可以了
            2.返回的结果,通用:List:存储多个结果,List.get(0):存储一个结果
            3.结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象。 Class
     */
    // 通用的查询方法
    // 参数:Class类对象,sql语句,sql语句占位符要复制的参数
    public <T> List <T> executeQuery(Class<T> clazz,String sql,Object...params) throws Exception{
        // 1.获取链接
        Connection connection = JDBC_utilV2.getConnection();

        // 2.编译SQL语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        // 3.设置占位符的值
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                preparedStatement.setObject(i+1,params[i]);
            }
        }

        // 4.执行SQL,接收返回的结果集
        ResultSet resultSet = preparedStatement.executeQuery();

        // 5.获取结果集中的元数据(列)对象(只在工具类中会用到)
        //   metadata对象中包含了列的数量,每个列的名称
        ResultSetMetaData metaData = resultSet.getMetaData();
        // getColumnCount:从封装的metadata对象中拿到列数
        int columnCount = metaData.getColumnCount();
        // 6.准备集合存储遍历的对象
        List<T> list = new ArrayList<>();
        // 7.处理结果
        while (resultSet.next()) {
            // 循环一次代表一行数据,通过反射创建一个对象
            T t = clazz.newInstance();

            // 对对象的属性赋值
            // getColumnCount()列的数量,循环遍历当前行的列,循环几次,就有多少列
            for (int i = 1; i <= columnCount; i++) {
                // 通过下标获取列的值
                Object value = resultSet.getObject(i);
                // 获取列的value值,这个值就是t这个对象中的某一个属性
                // 获取当前拿到的列的名字 = 对象的属性
                String fieldName = metaData.getColumnLabel(i);
                // 通过类对象和fieldName获取封装的类对象的属性
                Field field = clazz.getDeclaredField(fieldName);
                // 突破封装的private,取消属性的封装检查
                field.setAccessible(true);
                // 以属性赋值
                field.set(t,value);
            }
            // 将遍历得到的数据存储在集合中
            list.add(t);
        }
        // 资源的关闭
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()){
            JDBC_utilV2.release();
        }
        return list;
    }

③ 单行查询方法优化

    /*
        通用查询:在上面查询的集合结果中获取第一个结果,简化了单行单列的获取、单行多列的获取
     */
    public <T> T executeQueryBean(Class<T> clazz,String sql,Object...params) throws Exception{
        List<T> list = this.executeQuery(clazz,sql,params);
        if (list != null || list.size() == 0) {
            return null;
        }
        return list.get(0);
    }

BaseDao层代码

package JDBC3_Senior.dao;

import JDBC3_Senior.util.JDBC_utilV2;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

// 将共性的数据库操作代码封装在BaseDao层
public class BaseDao {
    /*
        通用的增删改的方法。
        @param sql 调用者要执行的SQL语句
        @param params SQL语句中的占位符要赋值的参数
        @return 受影响的行数
     */
    // 通用的修改方法  数组中的可变长参数
    public int executeUpdate(String sql,Object...params) throws Exception{
        // 1.通过JDBCUtilV2获取数据库链接
        Connection connection = JDBC_utilV2.getConnection();

        // 2.预编译SQL语句,sql语句由外部传入
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        // 3.为占位符赋值,执行SQL,接收返回结果
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                // 循环操作给占位符赋值
                // 占位符是从1开始的,参数数组下标从0开始
                // 用setObject为类型进行赋值,object通用类型
                // 参数顺序不同
                preparedStatement.setObject(i+1,params[i]);
            }
        }
        // 拿到受影响的行数
        int i = preparedStatement.executeUpdate();

        // 4.释放资源
        preparedStatement.close();
        // 如果事务开启
        if (connection.getAutoCommit()){
            JDBC_utilV2.release();
        }
        // 5.返回结果
        return i;
    }

    /*
        通用的查询:多行多列、单行多列、单行单列
            多行多列:List<Employee>
            单行多列:Employee
            单行单列:封装的是一个结果:Double、Integer、......
         封装过程:
            1.返回的类型:泛型:类型不确定,调用者知道,调用时,将此次查询的结果类型告知BaseDAO就可以了
            2.返回的结果,通用:List:存储多个结果,List.get(0):存储一个结果
            3.结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象。 Class
     */
    // 通用的查询方法
    // 参数:Class类对象,sql语句,sql语句占位符要复制的参数
    public <T> List <T> executeQuery(Class<T> clazz,String sql,Object...params) throws Exception{
        // 1.获取链接
        Connection connection = JDBC_utilV2.getConnection();

        // 2.编译SQL语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        // 3.设置占位符的值
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                preparedStatement.setObject(i+1,params[i]);
            }
        }

        // 4.执行SQL,接收返回的结果集
        ResultSet resultSet = preparedStatement.executeQuery();

        // 5.获取结果集中的元数据(列)对象(只在工具类中会用到)
        //   metadata对象中包含了列的数量,每个列的名称
        ResultSetMetaData metaData = resultSet.getMetaData();
        // getColumnCount:从封装的metadata对象中拿到列数
        int columnCount = metaData.getColumnCount();
        // 6.准备集合存储遍历的对象
        List<T> list = new ArrayList<>();
        // 7.处理结果
        while (resultSet.next()) {
            // 循环一次代表一行数据,通过反射创建一个对象
            T t = clazz.newInstance();

            // 对对象的属性赋值
            // getColumnCount()列的数量,循环遍历当前行的列,循环几次,就有多少列
            for (int i = 1; i <= columnCount; i++) {
                // 通过下标获取列的值
                Object value = resultSet.getObject(i);
                // 获取列的value值,这个值就是t这个对象中的某一个属性
                // 获取当前拿到的列的名字 = 对象的属性
                String fieldName = metaData.getColumnLabel(i);
                // 通过类对象和fieldName获取封装的类对象的属性
                Field field = clazz.getDeclaredField(fieldName);
                // 突破封装的private,取消属性的封装检查
                field.setAccessible(true);
                // 以属性赋值
                field.set(t,value);
            }
            // 将遍历得到的数据存储在集合中
            list.add(t);
        }
        // 资源的关闭
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()){
            JDBC_utilV2.release();
        }
        return list;
    }

    /*
        通用查询:在上面查询的集合结果中获取第一个结果,简化了单行单列的获取、单行多列的获取
     */
    public <T> T executeQueryBean(Class<T> clazz,String sql,Object...params) throws Exception{
        List<T> list = this.executeQuery(clazz,sql,params);
        if (list != null || list.size() == 0) {
            return null;
        }
        return list.get(0);
    }
}

测试类

    @Test
    public void testEmployeeDao() throws Exception {
        // 1.创建Dao实现类对象 多态的体现
        EmployeeDao employeeDao = new EmployeeDaoImpl();

        // 2.调用方法
        List<Employee> employeeList = employeeDao.selectAll();

        // 3.处理结果
        for (Employee employee : employeeList) {
            System.out.println("employee : " + employee);
        }
    }

三、事务

1、事务回顾

        数据库事务就是一种SQL语句执行的缓存机制,不会单条执行完毕就更新数据库数据,最终根据缓存内的多条语句执行结果统一判定。一个事务内所有语句都成功即事务成功,

        我们可以触发commit提交事务来结束事务,更新数据。一个事务内任意一条语句失败,即为事务失败

        我们可以触发rolback回滚事务结束事务,数据回到事务之前状态

一个业务涉及多条修改数据库语句。

例如:
        ① 经典的转账案例,转账业务(A账户减钱和B账户加钱,要一起成功)
        ② 批量删除(涉及多个删除)
        ③ 批量添加(涉及多个插入)

事务的特性:

        ① 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生, 要么都不发生。

        ② 一致性(Consistency):事务必须使数据库从一个一致性状态变换到另外一个一致性状态。

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

        ④ 持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的, 接下来的其他操作和数据库故障不应该对其有任何影响·

事务的影响

        ① 提交事务:让当前连接的操作,提交,对数据库产生影响,持久性的影响

        ② 回滚事务:让当前连接的操作,回滚到数据修改之前的状态

事务的提交方式:

        ① 自动提交:每条语句自动存储一个事务中,执行成功自动提交,执行失败自动回滚

        ② 手动提交:手动开启事务,添加语句,手动提交或者手动回滚即可。

--查看当前连接,事务的提交方式,(ON 自动提交、oFF 关闭自动提交,需要手动提交)
SHOW VARIABLES LIKE 'autocommit'

--0、false 都是设置当前连接的事务提交方式为手动提交
SET autocommit = FALSE:

--提交事务
COMMIT ;

--回滚事务
ROLLBACK;

--每一条DML语句,增删改语句都是一个事务

2.JDBC中事务的实现

关键代码

try{
    connection.setAutoCommit(false); //关闭自动提交了
/*
    connection.setAutoCommit(false)也就类似于SET autocommit = off

    注意,只要当前connection对象,进行数据库操作,都不会自动提交事务
    数据库动作:
        prepareStatement - 单一的数据库动作crud
        connection - 操作事务
        所有操作执行正确,提交事务!
*/
    connection.commit();
}catch(Execption e){
    //出现异常,则回滚事务!
    connection.roliback();
}

JDBC事务代码实现

设计一个ATM机存取款操作

① 数据库准备

use javawebjdbc;

CREATE TABLE t_bank(
   id INT PRIMARY KEY AUTO_INCREMENT COMMENT '账号主键',
   account VARCHAR(20) NOT NULL UNIQUE COMMENT '账号',
   money  INT UNSIGNED COMMENT '金额,不能为负值'
 ) ;
   
INSERT INTO t_bank(account,money) VALUES
  ('zhangsan',1000),('lisi',1000);

 

② 银行Dao层实现bankDao

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

// 将共性的数据库操作代码封装在BaseDao层
public class BaseDao {
    /*
        通用的增删改的方法。
        @param sql 调用者要执行的SQL语句
        @param params SQL语句中的占位符要赋值的参数
        @return 受影响的行数
     */
    // 通用的修改方法  数组中的可变长参数
    public int executeUpdate(String sql,Object...params) throws Exception{
        // 1.通过JDBCUtilV2获取数据库链接
        Connection connection = JDBC_utilV2.getConnection();

        // 2.预编译SQL语句,sql语句由外部传入
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        // 3.为占位符赋值,执行SQL,接收返回结果
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                // 循环操作给占位符赋值
                // 占位符是从1开始的,参数数组下标从0开始
                // 用setObject为类型进行赋值
                // 参数顺序不同
                preparedStatement.setObject(i+1,params[i]);
            }
        }
        // 拿到受影响的行数
        int i = preparedStatement.executeUpdate();

        // 4.释放资源
        preparedStatement.close();
        if (connection.getAutoCommit()){
            JDBC_utilV2.release();
        }
        // 5.返回结果
        return i;
    }

    /*
        通用的查询:多行多列、单行多列、单行单列
            多行多列:List<Employee>
            单行多列:Employee
            单行单列:封装的是一个结果:Double、Integer、......
         封装过程:
            1.返回的类型,泛型:类型不确定,调用者知道,调用时,将此次查询的结果类型告知BaseDAO就可以了
            2.返回的结果,通用:List:存储多个结果,get(0):存储一个结果
            3.结果的封装:反射,要求调用者告知BaseDAO要封装对象的类对象。 Class
     */
    // 通用的查询方法
    // 参数:Class类对象,sql语句,sql语句占位符要复制的参数
    public <T> List <T> executeQuery(Class<T> clazz,String sql,Object...params) throws Exception{
        // 1.获取链接
        Connection connection = JDBC_utilV2.getConnection();

        // 2.编译SQL语句
        PreparedStatement preparedStatement = connection.prepareStatement(sql);

        // 3.设置占位符的值
        if (params != null && params.length > 0) {
            for (int i = 0; i < params.length; i++) {
                preparedStatement.setObject(i+1,params[i]);
            }
        }

        // 4.执行SQL,接收返回的结果集
        ResultSet resultSet = preparedStatement.executeQuery();

        // 5.获取结果集中的元数据对象(只在工具类中会用到)
        //   metadata对象中包含了列的数量,每个列的名称
        ResultSetMetaData metaData = resultSet.getMetaData();
        int columnCount = metaData.getColumnCount();
        // 6.准备集合存储遍历的对象
        List<T> list = new ArrayList<>();
        // 7.处理结果
        while (resultSet.next()) {
            // 循环一次代表一行数据,通过反射创建一个对象
            T t = clazz.newInstance();

            // 对对象的属性赋值
            // getColumnCount()列的数量,循环遍历当前行的列,循环几次,就有多少列
            for (int i = 0; i < columnCount; i++) {
                // 通过下标获取列的值
                Object value = resultSet.getObject(i + 1);
                // 获取列的value值,这个值就是t这个对象中的某一个属性
                // 获取当前拿到的列的名字 = 对象的属性
                String fieldName = metaData.getColumnLabel(i);
                // 通过类对象和fieldName获取封装的类对象的属性
                Field field = clazz.getDeclaredField(fieldName);
                // 突破封装的private,取消属性的封装检查
                field.setAccessible(true);
                // 以属性赋值
                field.set(t,value);
            }
            // 将遍历得到的数据存储在集合中
            list.add(t);
        }
        // 资源的关闭
        resultSet.close();
        preparedStatement.close();
        if (connection.getAutoCommit()){
            JDBC_utilV2.release();
        }
        return list;
    }

    /*
        通用查询:在上面查询的集合结果中获取第一个结果,简化了单行单列的获取、单行多列的获取
     */
    public <T> T executeQueryBean(Class<T> clazz,String sql,Object...params) throws Exception{
        List<T> list = this.executeQuery(clazz,sql,params);
        if (list != null && list.size() == 0) {
            return null;
        }
        return list.get(0);
    }
}

测试用例 

    @Test
    public void testTransaction() throws Exception {
        // 接口引用实现链接对象
        BankDao bankDao = new BankDaoImpl();
        Connection con1 = null;
        try{
            // 1.获取链接,将连接的事务提交转变为手动提交
            con1 = JDBC_utilV2.getConnection();
            con1.setAutoCommit(false);// 开启事务

            // 2.操作减钱
            bankDao.subMoney(1,100);

            // 3.操作加钱
            bankDao.addMoney(2,100);

            // 4.提交事务
            con1.commit();
        }catch (SQLException e){
            try{
                // 事务回滚
                con1.rollback();
            }catch (SQLException ex){
                throw new RuntimeException(ex);
            }
        }finally {
            JDBC_utilV2.release();
        }
    }

如果想在JDBC中实现事务必须用到ThreadLocal

注意:当开启事务后,一定要根据代码的执行结果try…catch来决定事务提交还是回滚,否则数据库看不到数据的操作结果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2124956.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

批量操作Excel的四个方法(求和、移动、对比、合并)

Excel文件肯定少不了保存大量数据&#xff0c;那么在使用excel的时候会不会要大批量数据进行操作&#xff1f;今天分享4个快速使用excel操作的小技巧。希望能够帮大家提高excel制作效率。 技巧一&#xff1a;快速求和 当你想要分别得到行列的总和&#xff0c;我们可以选中表格…

WeChatFerry学习使用

准备 下载软件安装微信 安装python环境 conda create --prefixD:\PythonEnvs\wechatrobotstu python3.10 conda activate D:\PythonEnvs\wechatrobotstu使用 新建python项目 安装依赖包 pip install --upgrade wcferry -i https://pypi.doubanio.com/simple解压dll到一个…

安卓开发板_联发科MTK开发板使用ADB开发

1. ADB 使用 1.1. 前言 ADB&#xff0c;全称 Android Debug Bridge&#xff0c;是 Android 的命令行调试工具&#xff0c;可以完成多种功能&#xff0c;如跟踪系统日志&#xff0c;上传下载文件&#xff0c;安装应用等。 1.2. 准备连接 使用 adb时&#xff0c;你需要&#x…

转到大模型方向来得及吗?

最近不少同学问想搞大模型来得及吗&#xff1f;咨询的同学分成两类&#xff0c;一类是在公司的同学&#xff0c;一类是在校的同学。 第一&#xff0c;对于在校的同学。 一句话&#xff0c;能转到这个方向尽快转。今年校招包括招聘实习生&#xff0c;很多方向比如搜索推荐广告…

敏捷与企业架构:战略联盟

介绍 企业架构的三大支柱是对齐、洞察力和质量。 对齐&#xff1a;企业架构&#xff08;Enterprise Architecture&#xff09;使战略与运营、业务需求与IT供应保持一致&#xff0c;并确保这些变化符合企业战略和目标。 洞察力&#xff1a;企业架构提供对组织、信息系统和技术…

基于JavaWeb开发的Java+jquery+SpringMVC校园网站平台设计和实现

基于JavaWeb开发的JavajquerySpringMVC校园网站平台设计和实现 &#x1f345; 作者主页 网顺技术团队 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; &#x1f345; 文末获取源码联系方式 &#x1f4dd; &#x1f345; 查看下方微信号获取联系方式 承接各种…

shutil模块详解

shutil模块提供了一系列高级文件操作功能&#xff0c;包括复制、移动、删除和搜索文件或目录。shutil 模块对压缩包的处理是调用 ZipFile 和 TarFile这两个模块来进行的。 下面详细介绍并给出示例代码&#xff1a; 1. shutil.copy(src, dst) 复制文件&#xff0c;但不保留权限…

【程序员必读】如何用AI修复代码Bug,让你节省宝贵的调试时间!

在编程的旅程中&#xff0c;bug就像是我们前行路上的小石子&#xff0c;时不时地绊倒我们。无论你是刚入门的编程新手&#xff0c;还是经验丰富的开发者&#xff0c;调试代码时总会遇到各种各样的挑战。&#x1f629; 有时候&#xff0c;错误的信息可能模糊不清&#xff0c;令…

SAP PP模块后台配置全流程配置2

1.1.定义工艺路线 定义物料类型分配T-Code:OP50 为物料类型指定工艺路线类型 为物料类型HALF2、FERT2分配类型“路径N” 定义工艺路线CA01 1.1.2.1.定义HAL2类型:物料2000000000工艺路线 输入“物料编码”、“工厂”等信息 工艺路线:抬头信…

国家标准和行业标准有什么区别?如何办理国家标准?

在当今复杂多样的标准体系中&#xff0c;国家标准和行业标准犹如两颗璀璨的明珠&#xff0c;各自闪耀着独特的光芒&#xff0c;它们共同为经济社会的发展提供了坚实的技术支撑。然而&#xff0c;你是否真正了解这两者之间的区别呢&#xff1f; 一、制定主体 • 国家标准&#x…

0基础?没问题!吴恩达教授的《开发者的LLM入门完全指南》来了!

项目&#xff1a;面向开发者的 LLM 入门课程 这份完整版的大模型 AI 学习资料已经上传CSDN&#xff0c;朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费】 ## 项目简介 本项目是一个面向开发者的 LLM 入门教程&#xff0c;基于吴恩达老师大模型系列课…

问题:vite首次加载慢

概述&#xff1a; 不是说vite项目的启动很快很快吗&#xff1f; vite项目的启动确实是快&#xff08;注意这里的启动是指命令行启动完毕&#xff0c;不是指启动完之后首页加载完毕&#xff09; 如果某个界面是首次进入&#xff0c;且依赖比较多/比较复杂的话&#xff0c;那…

温习mysql函数 连接查询

字符串 1、CONCAT(S1,S2,...Sn) &#xff1a;字符串拼接&#xff0c;将S1 &#xff0c; S2 &#xff0c; ... Sn 拼接成一个字符串】 2、LOWER(str) &#xff1a;将字符串str全部转为小写 3、UPPER(str) &#xff1a;将字符串str全部转为大写 4、LPAD(str,n,pad)&#xff1a; …

基于SpringBoot+Vue+MySQL的教学资源共享平台

系统展示 用户前台界面 管理员后台界面 系统背景 随着信息技术的迅猛发展&#xff0c;教育领域对高效、便捷的教学资源需求日益增长。传统教学模式已难以满足当前教育的多样化需求&#xff0c;特别是在资源共享与利用方面存在明显不足。因此&#xff0c;构建一个基于SpringBoot…

关于大模型在产品开发中所面临的问题,利用大模型技术解决很简单!

“ 具体问题具体分析&#xff0c;大模型技术没有统一的解决方案 ” 有人说2024年是大模型应用的元年&#xff0c;而大模型在未来的发展潜力毋庸置疑&#xff0c;这也就意味着人工智能技术是下一个风口&#xff0c;因此各种各样基于大模型技术的创业公司如雨后春笋般涌现。 从…

Linux云计算 |【第二阶段】SHELL-DAY5

主要内容&#xff1a; awk命令、内置变量&#xff08;FS、$0、$1、$2、NF、NR&#xff09;、过滤时机&#xff08;BEGIN{}、{}、END{}&#xff09;、处理条件&#xff08;正则、&&、||、~\!~、等&#xff09;、awk数组、监控脚本、安全检测脚本 一、awk介绍 awk 是一…

【主机入侵检测】Wazuh解码器详解

前言 Wazuh 是一个开源的安全平台&#xff0c;它使用解码器&#xff08;decoders&#xff09;来从接收到的日志消息中提取信息。解码器将日志信息分割成字段&#xff0c;以便进行分析。Wazuh解码器使用XML语法&#xff0c;允许用户指定日志数据应该如何被解析和规范化。解码器的…

TP发邮件的功能如何实现?tp框架发送邮件?

tp发邮件系统如何设置发信&#xff1f;tp配置邮箱发送邮件方法&#xff1f; TP发邮件的功能&#xff0c;作为企业级应用中的一个关键模块&#xff0c;其稳定性和高效性直接影响到企业的日常运营。AokSend将深入探讨TP发邮件的功能如何实现&#xff0c;从基础配置到高级应用&am…

监控易监测对象及指标之:全面监控Oracle数据库

随着企业业务的不断增长和复杂化&#xff0c;Oracle数据库作为关键的业务数据管理系统&#xff0c;其性能和稳定性对于保障业务连续性至关重要。为了确保Oracle数据库的高效运行和稳定性能&#xff0c;对其进行全面监控成为了一项必要的工作。本文将基于监控易工具&#xff0c;…

搭建大模型知识库流程,以及基于langchain实现大模型知识库案例

“ RAG检索增强生成是为了解决大模型知识不足的问题 ” 大模型主要面临三个问题&#xff1a; 垂直领域内的知识不足 大模型知识有时间限制 大模型幻觉问题 第一个问题产生的原因是因为&#xff0c;没有经过垂直领域数据训练的大模型普遍表现不好&#xff1b;其次是目前的大…