JDBC(Java database connectivity)

1. JDBC简介

  • JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库,(java.sql,javax.sql)使用这些类库可以以一种标准的方法、方便地访问数据库资源。

  • JDBC为访问不同的数据库提供了一种统一的途径,为开发者屏蔽了一些细节问题。

  • JDBC的目标是使Java程序员使用JDBC可以连接任何提供了JDBC驱动程序的数据库系统,这样就使得程序员无需对特定的数据库系统的特点有过多的了解,从而大大简化和加快了开发过程。

2. 数据库的连接方式举例:

package www.fancy.connection;

import org.junit.Test;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.Properties;

/**
 *  @Name 
 *  @Description 获取数据库连接方式一
 *  @Author Fancy
 *  @return 2022/3/23 
 *  @Version 1.0
 */
public class ConnectionTest {
    @Test
    public void testConnection1() throws SQLException {

        Driver driver = new com.mysql.jdbc.Driver();

        String url = "jdbc:mysql://localhost:3306/test";
        //- **jdbc:子协议:子名称**
        //- **协议**:JDBC URL中的协议总是jdbc
        //- **子协议**:子协议用于标识一个数据库驱动程序
        //- **子名称**:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了**定位数据库**提供足够的信息。包含**主机名**(对应服务端的ip地址)**,端口号,数据库名**
        Properties info =new Properties();
        info.setProperty("user","localhost");
        info.setProperty("password","123456");


        //获取了数据库的连接
        Connection conn  = driver.connect(url,info);

        System.out.println(conn);
    }

    /**
     *  @Name
     *  @Description 获取数据库连接方式二
     *  @Author Fancy
     *  @return 2022/3/23
     *  @Version 1.0
     */
    @Test
    public void testConnection2() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        //使用反射,获取Driver类的对象
        Class clazz = Class.forName("com.mysql.jdbc.Driver");
        Driver driver = (Driver) clazz.newInstance();
        String url = "jdbc:mysql://localhost:3306/test";
        //- **jdbc:子协议:子名称**
        //- **协议**:JDBC URL中的协议总是jdbc
        //- **子协议**:子协议用于标识一个数据库驱动程序
        //- **子名称**:一种标识数据库的方法。子名称可以依不同的子协议而变化,用子名称的目的是为了**定位数据库**提供足够的信息。包含**主机名**(对应服务端的ip地址)**,端口号,数据库名**
        Properties info =new Properties();
        info.setProperty("user","localhost");
        info.setProperty("password","123456");


        //获取了数据库的连接
        Connection conn  = driver.connect(url,info);

        System.out.println(conn);
    }

    /**
     *  @Name
     *  @Description 获取数据库连接方式三:DriverManager
     *  @Author Fancy
     *  @return 2022/3/23
     *  @Version 1.0
     */
    @Test
    public void testConnection3() throws SQLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
        String url = "jdbc:mysql://localhost:3306/test";
        String user = "localhost";
        String password = "123456";

        Class.forName("com.mysql.jdbc.Driver");

        Connection conn = DriverManager.getConnection(url, user, password);
    }

    /**
     *  @Name
     *  @Descriptio 获取数据库连接方式四:将数据库连接需要的4个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
     *  实现了数据与代码的分离;
     *  如果需要修改配置文件信息,可以避免程序重新打包
     *  @Author Fancy
     *  @return 2022/3/23
     *  @Version 1.0
     */
    @Test
    public void testConnection4() throws Exception{
        //1.读取配置文件中的4个基本信息
        InputStream in = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties pros = new Properties();
            //加载文件
        pros.load(in);
        
        String user = pros.getProperty("user");
        String password = pros.getProperty("password");
        String url = pros.getProperty("url");
        String driverClass = pros.getProperty("driverClass");

        //2.加载驱动
        Class.forName(driverClass);

        //3.获取连接
        Connection conn = DriverManager.getConnection(url, user, password);
        System.out.println(conn);

    }
}

3.使用PreparedStatement实现CRUD操作

package www.fancy.connection;

import java.io.IOException;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;

/**
 *  @Name mysqlOutputTest
 *  @Description 获取数据库连接’tdkaoqin‘-’sample‘数据库-’sample‘,做查询操作。
 *  @Author Fancy
 *  @return 2022/3/24
 *  @Version 1.0
 */

public class mysqlOutputTest {
    public static void main(String[] args) throws IOException, ClassNotFoundException, SQLException {
        //1.获取数据库的连接
            //1.1读取配置文件’jdbc.properties‘
            //将配置文件变为字节输入流加载到当前类中
        InputStream in = mysqlOutputTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            //创建一个持久的属性集
        Properties pro = new Properties();
            //从输入流in中读取属性列表(键和元素对)
        pro.load(in);
            //读取连接数据库所需的属性,根据key读取value
        String user = pro.getProperty("user");//获取用户名
        String password = pro.getProperty("password");//获取密码
        String url = pro.getProperty("url");//获取mysql被注册的驱动程序的标识
        String driverClass = pro.getProperty("driverClass");//获取mysql的驱动

        //2.加载驱动,实例化Driver
        Class.forName(driverClass);

        //3.使用DriverManager来注册驱动(静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例)
        Connection conn = DriverManager.getConnection(url, user, password);//此时已成功建立了java程序和’tdkaoqin‘-’sample‘数据库-’sample‘表

        //4.操作和访问数据库
            //预编译sql语句,返回prepareStatement的实例
        //String sql = "SELECT * FROM ( SELECT P1.`样品名称`,P1.`样品批号`,P1.`检测项目`,P1.`样品储存条件`,P1.`样品含量范围`,P1.`样品成分性质`,P1.`是否含有干扰检测的物质`,P1.`样品前处理方式`,P1.`对应送样单编号`,P3.`项目编号`,P2.`备注` FROM((SELECT `样品名称`,`样品批号`,`检测项目`,`样品储存条件`,`样品含量范围`,`样品成分性质`,`是否含有干扰检测的物质`,`样品前处理方式`,`对应送样单编号` FROM sample WHERE `样品批号` IS NOT NULL) P1 LEFT JOIN  (SELECT `备注`,`对应送样单编号` FROM sample WHERE `备注` IS NOT NULL) P2 ON P1.`对应送样单编号` = P2.`对应送样单编号`) LEFT JOIN (SELECT `项目编号`,`对应送样单编号` FROM sample WHERE `项目编号` IS NOT NULL) P3 ON P3.`对应送样单编号`= P2.`对应送样单编号`) A WHERE A.`检测项目` LIKE('纯度%') AND A.`项目编号` LIKE('60%') ";
        String sql = "select "+"`name`"+",`price` FROM districtproducts WHERE "+"`price`"+" LIKE ('3%')";
        Statement st = conn.createStatement();
        ResultSet rs = st.executeQuery(sql);

        while (rs.next()){
            System.out.print(rs.getString("name")+",");
            System.out.println(rs.getString("price"));
        }
    }
}

封装数据库的连接、资源关闭的方法,为JDBC工具类

package www.fancy.connection;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;

/**
 *  @Name connection
 *  @Description 获取数据库的连接
 *  @Author Fancy
 *  @return 2022/3/24
 *  @Version 1.0
 */
public class JDBCUtils {

    public static Connection getConnection() throws Exception {
        //1.获取数据库的连接
        //1.1读取配置文件’jdbc.properties‘
        //将配置文件变为字节输入流加载到当前类中
        InputStream in = mysqlOutputTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
        //创建一个持久的属性集
        Properties pro = new Properties();
        //从输入流in中读取属性列表(键和元素对)
        pro.load(in);
        //读取连接数据库所需的属性,根据key读取value
        String user = pro.getProperty("user");//获取用户名
        String password = pro.getProperty("password");//获取密码
        String url = pro.getProperty("url");//获取mysql被注册的驱动程序的标识
        String driverClass = pro.getProperty("driverClass");//获取mysql的驱动

        //2.加载驱动,实例化Driver
        Class.forName(driverClass);

        //3.使用DriverManager来注册驱动(静态代码块中,会调用 DriverManager.registerDriver() 方法来注册自身的一个实例)
        Connection conn = DriverManager.getConnection(url, user, password);//此时已成功建立了java程序和’tdkaoqin‘-’sample‘数据库-’sample‘表
        return  conn;
    }

    /**
     *  @Name closeResource
     *  @Description 关闭资源的操作(连接statement)
     *  @Author Fancy
     *  @return 2022/3/24
     *  @Version 1.0
     */
    public void closeResource(Connection conn, Statement ps){
        try {
            if (conn != null)
            conn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        try {
            if (ps != null)
            ps.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
  •  JDBC API小结

  • 两种思想

    • 面向接口编程的思想

    • ORM思想(object relational mapping)

      • 一个数据表对应一个java类

      • 表中的一条记录对应java类的一个对象

      • 表中的一个字段对应java类的一个属性

    sql是需要结合列名和表的属性名来写。注意起别名。

  • 两种技术

    • JDBC结果集的元数据:ResultSetMetaData

      • 获取列数:getColumnCount()

      • 获取列的别名:getColumnLabel()

    • 通过反射,创建指定类的对象,获取指定的属性并赋值

JDBC事务处理

什么是事务?

  • 事务:一组逻辑操作单元,使数据从一种状态变换到另一种状态。

  • 事务处理(事务操作):保证所有事务都作为一个工作单元来执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久地保存下来;要么数据库管理系统将放弃所作的所有修改,整个事务回滚(rollback)到最初状态。

  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。

举例说明

package www.fancy.connection;

import org.junit.Test;

import java.lang.reflect.Field;
import java.sql.*;
import java.util.ArrayList;


public class transactionTest {
    @Test
    public void Test1() {
        Connection conn = null;
        try {
            conn = JDBCUtils.getConnection();
            //1.取消数据的自动提交
            conn.setAutoCommit(false);

            String sql1 = "Update addresses set family_id=family_id-101 WHERE family_id = ?";
            update(conn, sql1, 400);

            //System.out.println(10/0);

            String sql2 = "Update addresses set family_id=family_id+100 WHERE family_id = ?";
            update(conn, sql2, 300);

            System.out.println("successful");
            //2.提交数据
            conn.commit();
        } catch (Exception e) {
            e.printStackTrace();

            //3.回滚数据
            try {
                conn.rollback();
            } catch (SQLException ex) {
                ex.printStackTrace();
            }

        } finally {
            //使用数据库连接池时,要修改回自动提交数据
            try {
                conn.setAutoCommit(true);
            } catch (SQLException e) {
                e.printStackTrace();
            }
            JDBCUtils.closeResource(conn, null);
        }

    }

    //通用的增、删、改操作(体现一:增、删、改 ; 体现二:针对于不同的表)
    public void update(String sql, Object... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1.获取数据库的连接
            conn = JDBCUtils.getConnection();

            //2.获取PreparedStatement的实例 (或:预编译sql语句)
            ps = conn.prepareStatement(sql);
            //3.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }

            //4.执行sql语句
            ps.execute();
        } catch (Exception e) {

            e.printStackTrace();
        } finally {
            //5.关闭资源
            JDBCUtils.closeResource(conn, ps);
        }
    }

    public int update(Connection con, String sql, Object... args) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            //1.获取PreparedStatement的实例 (或:预编译sql语句)
            ps = conn.prepareStatement(sql);
            //2.填充占位符
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }

            //3.执行sql语句
            ps.execute();
        } catch (Exception e) {

            e.printStackTrace();
        } finally {
            //4.关闭资源
            JDBCUtils.closeResource(null, ps);
        }
        return 0;
    }

    //隔离级别
    @Test
    public void testTransaction2() throws Exception {
        Connection conn = JDBCUtils.getConnection();
        String sql = "update addresses ";
        testQuery4(conn,Addresses.class,sql,999);
    }

    /**
     *  @Name testQuery4
     *  @Description 针对于不同表的通用查询操作,泛型方法,考虑事务
     *  @Author Fancy
     *  @return 2022/3/25
     *  @Version 1.0
     */

    public <T> ArrayList<T> testQuery4(Connection conn,Class<T> clazz, String sql, Object...args) {

        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            //获取数据库fengfeng的连接

            ps = conn.prepareStatement(sql);
            //填充占位符,查询小于101的数据
            //ps.setObject(1,101);
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i+1,args[i]);

            }
            //获取结果集
            //在此PreparedStatement对象中执行SQL查询,并返回查询生成的ResultSet对象
            rs = ps.executeQuery();
            //获取结果集的元数据(修饰结果集的数据)
            ResultSetMetaData rsmd = rs.getMetaData();

            ArrayList arr = new ArrayList();
            //获取列数
            int columnCount = rsmd.getColumnCount();
            int j = 0;
            while (rs.next()){
                //通过反射造T对象
                arr.add(clazz.newInstance());
                for (int i = 0; i < columnCount; i++) {
                    //获取每一列的列值
                    Object columnValue = rs.getObject(i+1);
                    /*//获取每一列的列名
                    String columnName = rsmd.getColumnName(i+1);
                    这样写会报错因为mysql中的列名和java类中的属性名不一致
                    导致报错:java.lang.NoSuchFieldException: family_id
                    ,我们查询时应当使用别名,别名和类的属性名一直
                    并且方法换为getColumnNamelabel*/
                    String columnName = rsmd.getColumnLabel(i + 1);
                    //通过反射,将对象指定名columnName的属性赋值为指定的值columnValue
                    /*  返回一个Field对象,该对象反映由该类对象表示的类或接口的指定声明字段。name参数是一个字符串,用于指定所需字段的简单名称。
                     如果这个类对象代表数组类型,那么这个方法找不到数组类型的长度字段。
                          参数:name–字段的名称
                          返回:此类中指定字段的字段对象*/
                    Field field = clazz.getDeclaredField(columnName);
                    //将该field可访问
                    field.setAccessible(true);
                    //赋值
                    field.set(arr.get(j),columnValue);
                }
                j++;
            }
            return arr;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭资源
            JDBCUtils.closeResource(null,ps,rs);
        }
        return null;

    }
}

数据库的并发问题

  • 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:

    • 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。

    • 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。

    • 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。

  • 数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。

  • 一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。

  • Mysql 默认的事务隔离级别为: REPEATABLE READ

MySQL如何设置隔离级别?

    • 查看当前的隔离级别:

      SELECT @@tx_isolation;
    • 设置当前 mySQL 连接的隔离级别:

      set  transaction isolation level read committed;
    • 设置数据库系统的全局的隔离级别:

      set global transaction isolation level read committed;
    • 补充操作:

      • 创建mysql数据库用户:

        create user tom identified by 'abc123';
      • 授予权限

        #授予通过网络方式登录的tom用户,对所有库所有表的全部权限,密码设为abc123.
        grant all privileges on *.* to tom@'%'  identified by 'abc123'; 
        ​
         #给tom用户使用本地命令行方式,授予atguigudb这个库下的所有表的插删改查的权限。
        grant select,insert,delete,update on atguigudb.* to tom@localhost identified by 'abc123'; 
        ​

DAO及相关实现类

定义及作用

  • DAO:Data Access Object访问数据信息的类和接口,包括了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO

  • 作用:为了实现功能的模块化,更有利于代码的维护和升级。

层次结构图

 

数据库连接池

以上模式存在的问题

1.数据库的连接资源并没有得到很好的重复利用

2.对于每一次数据连接,使用完后都得断开

3.这种开发不能控制被创建的连接对象数

数据库连接池:

基本思想:为数据库建立一个“缓冲池”、需要时取出一个,使用完毕后再放回去

数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据连接,而不是重新建立一个

数据库连接的数量是由最小数据库连接数来设定的,连接池的最大数据库连接数量限定了这个连接池占有的最大连接数

多种开源的数据库连接池

DBCP/C3P0/Druid

package www.fancy.connectionpool;
import com.mchange.v2.c3p0.*;
import org.junit.Test;

public class PoolTest {
      @Test
      public void testGetConnection() throws Exception{
            ComboPooledDataSource cpds = new ComboPooledDataSource();
            cpds.setDriverClass( "com.mysql.jdbc.Driver" );//loads the jdbc driver
            cpds.setJdbcUrl( "jdbc:mysql://localhost:3306/fengfeng" );
            cpds.setUser("localhost"); cpds.setPassword("123456");
            //通过设置参数,对数据库连接池进行管理
            //设置初始时数据库连接池中的连接数
            cpds.setInitialPoolSize(10);

            System.out.println(cpds.getConnection());
      }
}
posted @ 2022-03-23 22:30  Fancy[love]  阅读(69)  评论(0编辑  收藏  举报