JDBC复习

JDBC 基础

1. JDBC 概述


基本概念:

  • JDBC:Java Database Connectivity,Java 数据库连接
  • JDBC 是 Java 提供的一组独立于任何数据库管理系统的 API
  • Java提供接口规范,由各个数据库厂商提供接口实现,厂商提供的实现类封装成 jar 文件,也就是我们俗称的数据库驱动 jar 包

lib 包和 resource 包:

  • lib 包放各种第三方 jar 包
  • resource 放 配置文件

jar包原理比如 java.sql 就是 java 内部定义的一套标准接口,然后我们导入的 jar 包就是第三方的实现类。

JDBC 编写步骤

在这里插入图片描述

2. Driver


注意导入 mysql-connector 包到 lib 文件夹

2.1 加载并注册驱动

语法

//加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");

/*
底层会调用
DriverManager.registerDriver(com.mysql.jdbc.Driver)
自动注册驱动
*/

2.2 建立数据库连接

连接三要素

  • URL:用于标识一个被注册的驱动程序,驱动程序管理器通过这个 URL 选择正确的驱动程序,从而建立到数据库的连接。

  • 数据库用户名

  • 数据库密码

URL组成

**JDBC URL的标准由三部分组成,各部分间用冒号分隔。 **

  • 协议:JDBC URL中的协议总是jdbc
  • 子协议:子协议用于标识一个数据库驱动程序
  • 子名称:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了定位数据库提供足够的信息。包含主机名(对应服务端的ip地址),端口号,数据库名

在这里插入图片描述

语法

//第一种: 硬编码
String url = "jdbc:mysql://localhost:3306/test";
String user = "root";
String password = "Ting123321";
Connection connection = DriverManager.getConnection(url, user, password);


//第二种: 软编码写入 properties
Properties pro = new Properties();
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties");
pro.load(is);
String url = pro.getProperty("url");
String user = pro.getProperty("user");
String password = pro.getProperty("password");
Connection connection = DriverManager.getConnection(url, user, password);

/*
jdbc.properties 文件

user=root
password=abc123
url=jdbc:mysql://localhost:3306/test
driverClass=com.mysql.jdbc.Driver

*/

3. Connection


作用

  • 获取执行 SQL 的对象
  • 管理事务

3.1 获执行 SQL 的对象

语法

//获取 普通执行SQL对象: Statement
Statement statement = connection.createStatement();

//获取 预编译SQL执行对象: preparedStatement(sql)
PreparedStatement preparedStatement = connection.prepareStatement(sql);

//获取执行存储过程的对象: CallableStatement(sql) 
CallableStatement callableStatement = connection.prepareCall(sql);

3.2 事务管理

语法:

//开启事务: true 自动提交, false 手动提交(就是开启)
connection.setAutoCommit(true); 

//提交事务
connection.commit();

//回滚事务
connection.rallback();

导致事务自动提交的操作

  • DDL操作一旦执行,都会自提交
  • DML默认情况下,一旦提交,就会自动提交
    • 我们可以通过 set autocommit = false 的方式取消 DML 操作的自动提交
  • 默认在关闭连接时,会自动提交数据

事务操作步骤:

  • 调用 Connection 对象的 setAutoCommit(false); 以取消自动提交事务
  • 在所有的 SQL 语句都成功执行后,调用 commit(); 方法提交事务
  • 在出现异常时,调用 rollback(); 方法回滚事务

若此时 Connection 没有被关闭,还可能被重复使用,则需要恢复其自动提交状态 setAutoCommit(true)。尤其是在使用数据库连接池技术时,执行close()方法前,建议恢复自动提交状态。

public void testJDBCTransaction() {
	Connection conn = null;
	try {
		// 1.获取数据库连接
		conn = JDBCUtils.getConnection();
		// 2.开启事务
		conn.setAutoCommit(false);
		// 3.进行数据库操作
		String sql1 = "update user_table set balance = balance - 100 where user = ?";
		update(conn, sql1, "AA");

		// 模拟网络异常
		//System.out.println(10 / 0);

		String sql2 = "update user_table set balance = balance + 100 where user = ?";
		update(conn, sql2, "BB");
		// 4.若没有异常,则提交事务
		conn.commit();
	} catch (Exception e) {
		e.printStackTrace();
		// 5.若有异常,则回滚事务
		try {
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
    } finally {
        try {
			//6.恢复每次DML操作的自动提交功能
			conn.setAutoCommit(true);
		} catch (SQLException e) {
			e.printStackTrace();
		}
        //7.关闭连接
		JDBCUtils.closeResource(conn, null, null); 
    }  
}

4. Statement 与 PreparedStatement


作用

  • 执行 SQL 语句

4.1 执行 SQL 语法

//执行 sql 语句, 
//如果是查询语句:true 有数据, false 没数据
//如果是修改语句: true 受影响条数大于0, false 受影响条数小于0
preparedStatement.execute();

//用于执行 DQL
//返回 ResultSet 结果集对象
preparedStatement.executeQuery();

//用于执行 DML,DDL 
//返回受影响条数
preparedStatement.executeUpdate();

4.2 Statement

//得到 SQL 语句
Statement statement = con.createStatement();

ResultSet resultSet = statement.executeQuery("SELECT * From t_emp");

int result = statement.executeUpdate("UPDATE t_emp set emp_name = 'angel' where emp_name = 

Statement 存在 SQL 注入问题

 String name = "张三";
 String sql = "SELECT * FROM users WHERE username = '" + name + "'";

//变成 String sql = "SELECT * FROM users WHERE username = 'abc' OR '1' = '1';
//如果用户输入的用户名是 abc' OR '1'='1 由于 abc' OR '1'='1
//这个条件总是为真,所以即使输入的密码不正确,也能够绕过验证登录系统,

4.3 PreparedStatement

//sql语句中的参数值用 ? 占位符替代
String sql = "select * from user where username = ? and password = ? "

//通过Connection 对象获取,并传入对应 SQL 
PreparedStatement preparedStatement = connection.prepareStatement(sql);

//设置参数值 
//参数值下标从 1 开始
preparedStatement.setString(1, "angel");
preparedStatement.setString(2, "123");

注意

  • setObject:方法能够处理各种不同的数据类型。例如,您传递了字符串 "莫扎特" 和整数 18 ,实际上在内部,setObject 会根据实际传入的对象类型进行适当的转换和处理。**
  • PreparedStatement是预编译SQL语句,同一SQL语句多次执行的情况下,可以复用,不必每次重新编译和解析。

setObject 能自动匹配,getObject 不行

5. ResultSet 与 ResultSetMetaData


作用:

  • 封装 DQL 查询语句的结果

5.1 ResultSet

语法

 //得到 SQL 语句
 /*
 	boolean next(): 最开始指向数据行的前一行,判断结果集的下一条是否有数据,如果有数据返回 true,	并且指针下移,返回 false 指针不会下移
 
	xxx getXXx(参数): 获取数据
	参数: 
		int 列的编号,从1开始
 		String 列的名称
 		
 */

        Statement statement = con.createStatement();
        ResultSet resultSet = statement.executeQuery("SELECT * From t_emp");

        while (resultSet.next()) {
            int emp_id = resultSet.getInt("emp_id");
            String emp_name = resultSet.getString("emp_name");
            double emp_salary = resultSet.getDouble("emp_salsary");
            int emp_age = resultSet.getInt("emp_age");
            System.out.println(emp_id + "\t" + emp_name + "\t" + emp_salary + "\t" + emp_age);
        }

5.2 ResultSetMetaData

作用

  • 可用于获取关于 ResultSet 对象中列的类型和属性信息的对象

    在这里插入图片描述

语法

  • ResultSetMetaData meta = resultset.getMetaData();
    • getColumnName(int column):获取指定列的名称

    • getColumnLabel(int column):获取指定列的别名,如果没有别名就获取原名

    • getColumnCount():返回当前 ResultSet 对象中的列数。

    • getColumnTypeName(int column):检索指定列的数据库特定的类型名称。

    • getColumnDisplaySize(int column):指示指定列的最大标准宽度,以字符为单位。

    • isNullable(int column):指示指定列中的值是否可以为 null。

    • isAutoIncrement(int column):指示是否自动为指定列进行编号,这样这些列仍然是只读的。

常用

如何获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 方法即可

获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 方法

获取 ResultSet 每一列的列的名字是什么:调用 ResultSetMetaData 的getColumnLabel() 方法

6. 批量操作


当需要成批插入或者更新记录时,可以采用Java的批量更新机制,这一机制允许多条语句一次性提交给数据库批量处理。通常情况下比单独提交处理更有效率

语法

PreparedStatement ps = conn.prepareStatement(sql);
//积攒需要批量处理的SQL语句
ps.addBatch()

//执行批量处理语句
ps.executeBatch()

//清空缓存的数据
ps.clearBatch()

举例

public class addBatchTest {
    @Test
    public void test1() throws Exception {
        long start = System.currentTimeMillis();

        //建立连接
        Connection connection = Utils.jdbcConnection();

        //执行 SQL
        String sql = "insert into goods(name)values(?)";
        PreparedStatement ps = connection.prepareStatement(sql);

        for(int i = 1;i <= 1000000;i++){
            ps.setString(1, "name_" + i);

            //1.“攒”sql
            ps.addBatch();
            if(i % 500 == 0){
                //2.执行
                ps.executeBatch();
                //3.清空
                ps.clearBatch();
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("花费的时间为:" + (end - start));

        //关闭资源
        Utils.closeConnection(null, ps, connection);

    }
}

7. 常见异常


7.1 SQL语句问题

  • SQL语句有错误,检查SQL语句!建议SQL语句在SQL工具中测试后再复制到Java程序中!
  • 连接数据库的URL中,数据库名称编写错误,也会报该异常!

在这里插入图片描述

7.2 SQL语句未设置参数问题

java.sql.SQLException:No value specified for parameter 1

在使用预编译SQL语句时,如果有?占位符,要为每一个占位符赋值,否则报该错误!

在这里插入图片描述

7.3 用户名或密码错误问题

连接数据库时,如果用户名或密码输入错误,也会报SQLException,容易混淆!所以一定要看清楚异常后面的原因描述

在这里插入图片描述

7.4 通信异常

在连接数据库的URL中,如果IP或端口写错了,会报如下异常:

com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure

在这里插入图片描述

8. JDBC 操作总结

1. 注册驱动【依赖的驱动类,进行安装】

2. 获取连接【Connection 建立连接】

3. 创建发送 SQL 语句对象【Connection创建发送SQL语句的Statement】

4. 发送SQL语句,并获取返回结果【Statement 发送 SQL 语句到数据库并取得返回结果】

5. 结果集解析【ResultSet结果集解析,将查询结果解析出来】

6. 资源关闭【释放 ResultSet, Statement, Connection】

public class Test {
    public static void main(String[] args) throws Exception{
        
        //1. 注册驱动【依赖的驱动类,进行安装】
        DriverManager.registerDriver(new Driver());

        //2. 获取连接【Connection 建立连接】
        String url = "jdbc:mysql://127.0.0.1:3306/atguigu";
        String user = "root";
        String password = "Ting123321";
        Connection con = DriverManager.getConnection(url, user, password);

        //3.创建发送 SQL 语句对象【Connection创建发送SQL语句的Statement】
       
        String sql = "SELECT * FROM t_emp WHERE emp_name = ?";
        PreparedStatement preparedStatement = con.prepareStatement(sql);
        preparedStatement.setString(1, "angel");

       
        //4. 发送SQL语句,并获取返回结果【Statement 发送 SQL 语句到数据库并取得返回结果】
        ResultSet resultSet = preparedStatement.executeQuery();
        
        //5. 结果集解析【ResultSet结果集解析,将查询结果解析出来】
        while (resultSet.next()) {
            int empId = resultSet.getInt("emp_id");
            String empName = resultSet.getString("emp_name");
            double empSalary = resultSet.getDouble("emp_salary");
            int empAge = resultSet.getInt("emp_age");
            System.out.println(empId + "\t" + empName + "\t" + empSalary + "\t" + empAge);
        }

        
        //6.资源关闭【释放 ResultSet, Statement, Connection】
        resultSet.close();
        preparedStatement.close();
        con.close();
    }
}

JDBC 进阶

1. ORM 思想


ORM 编程思想 (object relational mapping)

  • 一个数据表对应一个 java 类
  • 表中一条数据对应 java 类的一个对象
  • 表中的一个字段对应 java 类的一个属性

Bean 类

public class orderorm {
    private Integer id;
    private String orderName;
    private Date orderDate;
     //省略get、set、无参、有参、toString方法。
}    

实操

public class orderOrmTest {
    @Test
    public void test1 () throws Exception{
        //加载驱动
        Class.forName("com.mysql.cj.jdbc.Driver");

        //建立连接
        Properties properties = new Properties();
        properties.load(new FileReader("resource/jdbc.properties"));
        String url = properties.getProperty("url");
        String user = properties.getProperty("user");
        String password = properties.getProperty("password");
        Connection connection = DriverManager.getConnection(url, user, password);

        //预编译 SQL 语句
        //1.order 表和 sql 关键字冲突所以要 ``
        //2.因为 orm思想实现类里面名字是 orderId 所以要起别名
        String sql = "select order_id as orderId, order_name as orderName, order_date as orderDate from `order` where order_id = ?;";
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setObject(1, 2);

        //处理返回的 SQL 结果
        ResultSet resultSet = preparedStatement.executeQuery();
        if (resultSet.next()) {
            Integer orderId = resultSet.getInt(1);
            String orderName = resultSet.getString(2);
            Date orderDate = resultSet.getDate(3);
            //创建实体类
            orderorm ord = new orderorm(orderId, orderName, orderDate);
            System.out.println(ord);
        }

        //关闭资源
        resultSet.close();
        preparedStatement.clearBatch();
        connection.close();
    }
}

2. 数据库连接池


  • 连接池就是数据库连接对象的缓冲区,通过配置,由连接池负责创建连接、管理连接、释放连接等操作。

  • 预先创建数据库连接放入连接池,用户在请求时,通过池直接获取连接,使用完毕后,将连接放回池中,避免了频繁的创建和销毁,同时解决了创建的效率。

  • 当池中无连接可用,且未达到上限时,连接池会新建连接。

  • 池中连接达到上限,用户请求会等待,可以设置超时时间。

2.1 常见连接池

JDBC 的数据库连接池使用 javax.sql.DataSource接口进行规范,所有的第三方连接池都实现此接口,自行添加具体实现!也就是说,所有连接池获取连接的和回收连接方法都一样,不同的只有性能和扩展功能!

  • DBCP 是Apache提供的数据库连接池,速度相对C3P0较快,但自身存在一些BUG。
  • C3P0 是一个开源组织提供的一个数据库连接池,速度相对较慢,稳定性还可以。
  • Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能, 稳定性较c3p0差一点
  • Druid 是阿里提供的数据库连接池,是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,性能、扩展性、易用性都更好,功能丰富
  • Hikari(ひかり[shi ga li]) 取自日语,是光的意思,是SpringBoot2.x之后内置的一款连接池,基于 BoneCP (已经放弃维护,推荐该连接池)做了不少的改进和优化,口号是快速、简单、可靠。

在这里插入图片描述

在这里插入图片描述

2.2 Druid 连接池使用

2.2.1 Druid 硬编码实现
//1.创建DruidDataSource连接池对象。
DruidDataSource druidDataSource = new DruidDataSource();


//2.设置连接池配置信息
//设置驱动
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
//设置数据库地址
druidDataSource.setUrl("jdbc:mysql:///atguigu");
//数据库名称
druidDataSource.setUsername("root");
//数据库密码
druidDataSource.setPassword("Ting123321");

//3. 设置连接池参参数【非必须】
//设置最大连接池数量 默认是 8
druidDataSource.setMaxActive(int num);
//设置初始化时创建连接的数量
//如果将 initialSize 设置为 5 ,那么在连接池初始化时,会立即创建 5 个可用的连接。
druidDataSource.setInitialSize(int num);

//4.通过连接池获取连接对象
druidDataSource.getConnection();

public class DuridTest {
    @Test
    public void testHardCodeDruid() throws Exception {
        /*
            硬编码:将连接池的配置信息和 Java 代码耦合在一起
            1.创建DruidDateSoure连接池对象
            2.设置连接池的配置信息【一个必须参数|一个非必要参数】
            3.通过连接池获取连接对象
            4.回收连接【不是释放谅解,而是将连接归还连接池,给其他线程进行复用】
         */

        //1.创建DruidDataSource连接池对象。
        DruidDataSource druidDataSource = new DruidDataSource();

        //2.设置连接池的配置信息【必须 | 非必须】
        //2.1 必须设置的配置
        druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        druidDataSource.setUrl("jdbc:mysql:///atguigu");
        druidDataSource.setUsername("root");
        druidDataSource.setPassword("Ting123321");

        //2.2 非必须设置的配置
        druidDataSource.setInitialSize(10);
        druidDataSource.setMaxActive(20);

        //3.通过连接池获取连接对象
        Connection connection = druidDataSource.getConnection();
        System.out.println(connection);

        //基于connection进行CRUD

        //4.回收连接
        connection.close();

    }
}    
2.2.2 Druid 软编码实现

创建一个文件夹标记为 Resources 放配置文件的文件夹,然后把配置写进 XXX.properties 文件中放进去。我们就可以直接导入配置文件

在这里插入图片描述

Druid 软编码实现

创建一个文件夹标记为 Resources 放配置文件的文件夹,然后把配置写进 XXX.properties 文件中放进去。我们就可以直接导入配置文件

在这里插入图片描述

连接信息

//创造连接池
DruidDataSourceFactory.createDataSource(properties文件);
# druid连接池需要的配置参数,key固定命名
driverClassName=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///atguigu
username=root
password=atguigu
initialSize=10
maxActive=20
public void testResourceDruid() throws Exception {
        //1.创建Properties集合, 用于配置外部配文件的key和value值
        Properties properties = new Properties();

        //2.读取外部配置文件,获取输入流,加载到Properties集合里
        //从根目录开始找 db.properties 文件,根目录就是最外层目录
        InputStream inputStream = DuridTest.class.getClassLoader().getResourceAsStream("db.properties");
        properties.load(inputStream);

        //3.基于Properties集合构建DruidDataSoure连接池
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

        //4.通过连接池获取连接对象
        Connection connection = dataSource.getConnection();

        //5.开发 CRUD

        //6.回收连接
        connection.close();
    }

其他配置

配置缺省说明
name配置这个属性的意义在于,如果存在多个数据源,监控的时候可以通过名字来区分开来。 如果没有配置,将会生成一个名字,格式是:”DataSource-” + System.identityHashCode(this)
jdbcUrl连接数据库的url,不同数据库不一样。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username连接数据库的用户名
password连接数据库的密码。如果你不希望密码直接写在配置文件中,可以使用ConfigFilter。详细看这里:https://github.com/alibaba/druid/wiki/使用ConfigFilter
driverClassName根据url自动识别 这一项可配可不配,如果不配置druid会根据url自动识别dbType,然后选择相应的driverClassName(建议配置下)
initialSize0初始化时建立物理连接的个数。初始化发生在显示调用init方法,或者第一次getConnection时
maxActive8最大连接池数量
maxIdle8已经不再使用,配置了也没效果
minIdle最小连接池数量
maxWait获取连接时最大等待时间,单位毫秒。配置了maxWait之后,缺省启用公平锁,并发效率会有所下降,如果需要可以通过配置useUnfairLock属性为true使用非公平锁。
poolPreparedStatementsfalse是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
maxOpenPreparedStatements-1要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
validationQuery用来检测连接是否有效的sql,要求是一个查询语句。如果validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrowtrue申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturnfalse归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
testWhileIdlefalse建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
timeBetweenEvictionRunsMillis有两个含义: 1)Destroy线程会检测连接的间隔时间2)testWhileIdle的判断依据,详细看testWhileIdle属性的说明
numTestsPerEvictionRun不再使用,一个DruidDataSource只支持一个EvictionRun
minEvictableIdleTimeMillis
connectionInitSqls物理连接初始化的时候执行的sql
exceptionSorter根据dbType自动识别 当数据库抛出一些不可恢复的异常时,抛弃连接
filters属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有: 监控统计用的filter:stat日志用的filter:log4j防御sql注入的filter:wall
proxyFilters类型是List,如果同时配置了filters和proxyFilters,是组合关系,并非替换关系

2.3 Hikari 连接池

2.3.1 硬编码实现
@Test
    public void testHardCodeHikari() throws Exception {
        //1. 创建 HikariDataSource 连接池对象
        //2. 设置连接池配置信息【一个必须|一个非必须】
        //3. 通过连接池获取连接对象
        //4. 回收连接

        //1.创建 HikariDataSource 连接池对象
        HikariDataSource hikariDataSource = new HikariDataSource();

        //2.设置连接池配置信息
        //2.1必须设置
        hikariDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        hikariDataSource.setJdbcUrl("jdbc:mysql:///atguigu");
        hikariDataSource.setUsername("root");
        hikariDataSource.setPassword("Ting123321");

        //2.2 非必须设置
        hikariDataSource.setMinimumIdle(10);
        hikariDataSource.setMaximumPoolSize(20);

        //3.通过连接池获取连接对象
        Connection connection = hikariDataSource.getConnection();
        System.out.println(connection);

        //4.回收连接
        connection.close();

    }
2.3.2 软编码实现
//创建连接池
HikariConfig hikariConfig = new HikariConfig(properties文件);
//properties 文件
driverClassName=com.mysql.cj.jdbc.Driver
jdbcUrl=jdbc:mysql:///atguigu
username=root
password=Ting123321
minimumIdle=10
maximumPoolSize=20
@Test
    public void testResourcesHikari() throws Exception {
        //1. 创建配置 Properties 集合,用于配置外部配置文
        Properties properties = new Properties();

        //2.读取配置文件
        InputStream inputStream = HikariTest.class.getClassLoader().getResourceAsStream("hikari.properties");
        properties.load(inputStream);

        //3.创建 HikariConfig连接配置对象,将 Properties 对象传进去
        HikariConfig hikariConfig = new HikariConfig(properties);

        //4.基于 hikariConfig连接池配置对象,构建HikariDataSource
        HikariDataSource hikariDataSource = new HikariDataSource(hikariConfig);

        //5.获取连接
        Connection connection = hikariDataSource.getConnection();
        System.out.println(connection);

        //6.回收连接
        connection.close();

    }

HikariCP其他配置

属性默认值说明
isAutoCommittrue自动提交从池中返回的连接
connectionTimeout30000等待来自池的连接的最大毫秒数
maxLifetime1800000池中连接最长生命周期如果不等于0且小于30秒则会被重置回30分钟
minimumIdle10池中维护的最小空闲连接数 minIdle<0或者minIdle>maxPoolSize,则被重置为maxPoolSize
maximumPoolSize10池中最大连接数,包括闲置和使用中的连接
metricRegistrynull连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置
healthCheckRegistrynull报告当前健康信息
poolNameHikariPool-1连接池的用户定义名称,主要出现在日志记录和JMX管理控制台中以识别池和池配置
idleTimeout是允许连接在连接池中空闲的最长时间

JDBC 高级

1. 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<Connection> threadLocal = null;

//得到共享变量
threadLocal.get();

//设置共享变量
threadLocal.set(T t);

//移除共享变量
threadLocal.remove();

2. Dao


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

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

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

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

BaseDao

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

各种Dao都是基于 BaseDao

BaseDao -> XXXDao -> XXXDaoimpl

2.1 Utils 工具类

package com.atguigu.senior.util;

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工具类:
 *      1、维护一个连接池对象、维护了一个线程绑定变量的ThreadLocal对象
 *      2、对外提供在ThreadLocal中获取连接的方法
 *      3、对外提供回收连接的方法,回收过程中,将要回收的连接从ThreadLocal中移除!
 *  注意:工具类仅对外提供共性的功能代码,所以方法均为静态方法!
 *  注意:使用ThreadLocal就是为了一个线程在多次数据库操作过程中,使用的是同一个连接!
 */
public class JDBCUtilV2 {
    //创建连接池引用,因为要提供给当前项目的全局使用,所以创建为静态的。
    private static DataSource dataSource;
    private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();

    //在项目启动时,即创建连接池对象,赋值给dataSource
    static {
        try {
            Properties properties = new Properties();
            InputStream inputStream = JDBCUtil.class.getClassLoader().getResourceAsStream("db.properties");
            properties.load(inputStream);

            dataSource = DruidDataSourceFactory.createDataSource(properties);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    //对外提供在连接池中获取连接的方法
    public static Connection getConnection(){
        try {
            //在ThreadLocal中获取Connection、
            Connection connection = threadLocal.get();
            //threadLocal里没有存储Connection,也就是第一次获取
            if (connection == null) {
                //在连接池中获取一个连接,存储在threadLocal里。
                connection = dataSource.getConnection();
                threadLocal.set(connection);
            }
            return connection;

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    //对外提供回收连接的方法
    public static void release(){
        try {
            Connection connection = threadLocal.get();
            if(connection!=null){
                //从threadLocal中移除当前已经存储的Connection对象
                threadLocal.remove();
                //如果开启了事务的手动提交,操作完毕后,归还给连接池之前,要将事务的自动提交改为true
                connection.setAutoCommit(true);
                //将Connection对象归还给连接池
                connection.close();
            }
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

}

2.2 BaseDao

public abstract class BaseDAO {
    /*
    通用的增、删、改的方法
    String sql:sql
    Object... args:给sql中的?设置的值列表,可以是0~n
     */
    protected int update(String sql,Object... args) throws SQLException {
//        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?占位符的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1,args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        //执行sql
        int len = ps.executeUpdate();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //connection.getAutoCommit()为false,不要在这里回收connection,由开启事务的地方回收
        //connection.getAutoCommit()为true,正常回收连接
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.release();
        }
        return len;
    }

    /*
    通用的查询多个Javabean对象的方法,例如:多个员工对象,多个部门对象等
    这里的clazz接收的是T类型的Class对象,
    如果查询员工信息,clazz代表Employee.class,
    如果查询部门信息,clazz代表Department.class,
    返回List<T> list
     */
    protected <T> ArrayList<T> executeQuery(Class<T> clazz,String sql,Object... args) throws Exception {
        //        创建PreparedStatement对象,对sql预编译
        Connection connection = JDBCTools.getConnection();
        PreparedStatement ps = connection.prepareStatement(sql);
        //设置?的值
        if(args != null && args.length>0){
            for(int i=0; i<args.length; i++) {
                ps.setObject(i+1, args[i]);//?的编号从1开始,不是从0开始,数组的下标是从0开始
            }
        }

        ArrayList<T> list = new ArrayList<>();
        ResultSet res = ps.executeQuery();

        /*
        获取结果集的元数据对象。
        元数据对象中有该结果集一共有几列、列名称是什么等信息
         */
        ResultSetMetaData metaData = res.getMetaData();
        int columnCount = metaData.getColumnCount();//获取结果集列数

        //遍历结果集ResultSet,把查询结果中的一条一条记录,变成一个一个T 对象,放到list中。
        while(res.next()){
            //循环一次代表有一行,代表有一个T对象
            T t = clazz.newInstance();//要求这个类型必须有公共的无参构造

            //把这条记录的每一个单元格的值取出来,设置到t对象对应的属性中。
            for(int i=1; i<=columnCount; i++){
                //for循环一次,代表取某一行的1个单元格的值
                Object value = res.getObject(i);

                //这个值应该是t对象的某个属性值
                //获取该属性对应的Field对象
                //String columnName = metaData.getColumnName(i);//获取第i列的字段名
                //这里再取别名可能没办法对应上
                String columnName = metaData.getColumnLabel(i);//获取第i列的字段名或字段的别名
                Field field = clazz.getDeclaredField(columnName);
                field.setAccessible(true);//这么做可以操作private的属性

                field.set(t,value);
            }

            list.add(t);
        }

        res.close();
        ps.close();
        //这里检查下是否开启事务,开启不关闭连接,业务方法关闭!
        //没有开启事务的话,直接回收关闭即可!
        if (connection.getAutoCommit()) {
            //回收
            JDBCTools.release();
        }
        return list;
    }

    protected <T> T queryBean(Class<T> clazz,String sql, Object... args) throws Exception {
        ArrayList<T> list = query(clazz, sql,args);
        if(list == null || list.size() == 0){
            return null;
        }
        return list.get(0);
    }
}

2.3 EmployeeDao

package com.atguigu.senior.dao;

import com.atguigu.senior.pojo.Employee;

import java.util.List;

/**
 * EmployeeDao这个类对应的是t_emp这张表的增删改查的操作
 */
public interface EmployeeDao {
    /**
     * 数据库对应的查询所有的操作
     * @return 表中所有的数据
     */
    List<Employee> selectAll();

    /**
     * 数据库对应的根据empId查询单个员工数据操作
     * @param empId 主键列
     * @return 一个员工对象(一行数据)
     */
    Employee selectByEmpId(Integer empId);

    /**
     * 数据库对应的新增一条员工数据
     * @param employee ORM思想中的一个员工对象
     * @return 受影响的行数
     */
    int insert(Employee employee);

    /**
     * 数据库对应的修改一条员工数据
     * @param employee ORM思想中的一个员工对象
     * @return 受影响的行数
     */
    int update(Employee employee);

    /**
     * 数据库对应的根据empId删除一条员工数据
     * @param empId 主键列
     * @return 受影响的行数
     */
    int delete(Integer empId);
}

2.4 EmployeeDaoimpl

package com.atguigu.senior.dao.impl;

import com.atguigu.senior.dao.BaseDAO;
import com.atguigu.senior.dao.EmployeeDao;
import com.atguigu.senior.pojo.Employee;

import java.util.List;

public class EmployeeDaoImpl extends BaseDAO implements EmployeeDao {
    @Override
    public List<Employee> selectAll() {
        try {
            String sql = "SELECT emp_id empId,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp";
            return executeQuery(Employee.class,sql,null);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Employee selectByEmpId(Integer empId) {
        try {
            String sql = "SELECT emp_id empId,emp_name empName,emp_salary empSalary,emp_age empAge FROM t_emp where emp_id = ?";
            return executeQueryBean(Employee.class,sql,empId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int insert(Employee employee) {
        try {
            String sql = "INSERT INTO t_emp(emp_name,emp_salary,emp_age) VALUES (?,?,?)";
            return executeUpdate(sql,employee.getEmpName(),employee.getEmpSalary(),employee.getEmpAge());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int update(Employee employee) {
        try {
            String sql = "UPDATE t_emp SET emp_salary = ? WHERE emp_id = ?";
            return executeUpdate(sql,employee.getEmpSalary(),employee.getEmpId());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public int delete(Integer empId) {
        try {
            String sql = "delete from t_emp where emp_id = ?";
            return executeUpdate(sql,empId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

3. Apache-Dbutils


在这里插入图片描述

QueryRunner queryRunner =  new QueryRunner();
String sql = ...
//orm 思想的 Actor 表
Actor actor = queryRunner.query(Connection connection, String sql, ResultSetHander resultsetHander(类的反射), Object...args);

底层除了 Connection 要自己关,其他它会帮你关

3.1 查

例:返回List集合

        QueryRunner queryRunner = new QueryRunner();

        String sql = "select order_id as orderid, order_name as orderName, order_date as orderDate from `order` where order_id >= ?;";
        List<orderorm> query = queryRunner.query(connection, sql, new BeanListHandler<>(orderorm.class),1);

        for (orderorm orm : query) {
            System.out.println(orm);
        }

        connection.close();

    }

例: 返回单行单列

//2. 创建 QueryRunner
        QueryRunner queryRunner = new QueryRunner();

        String sql = "select order_id as orderid, order_name as orderName, order_date as orderDate from `order` where order_id = ?;";
       
		//order 表
        orderorm order = queryRunner.query(connection, sql, new BeanHandler<>(order.class), 1);

		System.out.println(order);       	

        connection.close();

    }

3.2 改(update, insert, delete)

语法

//三个全部封装在 update 里面
//返回受影响条数
queryRunner.update(Connection connection, String sql, Objecj...args);
  • 12
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值