JDBC是Java DataBase Connectivity的缩写,它是Java程序访问数据库的标准接口,用于执行SQL语句。使用Java程序访问数据库时,Java代码并不是直接通过TCP连接去访问数据库,而是通过JDBC接口来访问,而JDBC接口则通过JDBC驱动来实现真正对数据库的访问。在Java中,数据库存取技术只能通过JDBC访问数据

JDBC访问数据库的方式

  1. 直接使用JDBC的API去访问数据库服务器(MySQL/Oracle)
  2. 间接地使用JDBC的API去访问数据库服务器

JDBC步骤

  1. 注册加载驱动程序
  2. 获得数据库连接
  3. 获取执行对象
  4. 执行sql语句
  5. 释放资源
public class JDBCTest1 {
    @Test
    public void testCreatJDBC() throws SQLException, ClassNotFoundException {
        final String URL = "jdbc:mysql:///test";
        final String user = "root";
        final String password = "root123456";

        // 1.注册加载驱动程序
        // 使用反射注册,源码中有静态代码块new Driver对象
        Class.forName("com.mysql.jdbc.Driver"); // 在mySQL 5 版本后,会自动注册,不需要手动注册

        // 2.获得数据库连接
        Connection connection = DriverManager.getConnection(URL, user, password);

        // 3.获取执行对象
        Statement stmt = connection.createStatement();

        // 4.执行sql查询语句,返回结果集
        ResultSet rs = stmt.executeQuery("SELECT * FROM emp");

        // 5.如果有数据,rs.next()返回true
        while (rs.next()){
            System.out.println(rs.getInt("empno")+","+rs.getString("ename")+","+rs.getInt("sal"));
        }
    }
}

注册加载驱动程序

在mySQL5版本前,需要注册驱动后才可以连接数据库,在之后的版本mySQL的jar包中新的Driver()方法会自动注册。

注册加载驱动程序有多种方法:

  1. 使用DriverManager注册,看起来比较直观的一种方式,注册相应的db的jdbc驱动,在编译时需要导入对应的lib

    DriverManager.register(new com.mysql.jdbc.Driver());
    
  2. 使用放射方法注册

    Class.forName(com.mysql.jdbc.Driver);
    
  3. 通过系统的属性设置

    System.setProperty(jdbc.driver,com.mysql.jdbc.Driver);
    

获得数据库连接

获取数据库连接分为两种方式:普通连接、通过连接池进行连接。

  • 普通连接

    1. 使用Driver连接
    //获取Driver实现类对象
    Driver driver = new com.mysql.jdbc.Driver();
    String url = "jdbc:mysql://localhost:3306/jdbc";
    Properties info = new Properties();
    info.setProperty("user","root");
    info.setProperty("password","root123456");
    Connection connection = driver.connect(url,info);
    
    1. 使用DriverManager连接
    Connection connection = DriverManager.getConnection(url, user, password);
    
  • 连接池连接

    数据库连接是一种关键的有限的昂贵的资源,这一点在多用户的网页应用程序中体现得尤为突出。 一个数据库连接对象均对应一个物理数据库连接,每次操作都打开一个物理连接,使用完都关闭连接,这样造成系统的性能低下。

    数据库连接池的解决方案是在应用程序启动时建立足够的数据库连接,并将这些连接组成一个连接池,由应用程序动态地对池中的连接进行申请、使用和释放

    对于多于连接池中连接数的并发请求,应该在请求队列中排队等待。并且应用程序可以根据池中连接的使用率,动态增加或减少池中的连接数。连接池技术尽可能多地重用了消耗内存地资源,大大节省了内存,提高了服务器地服务效率,能够支持更多的客户服务。通过使用连接池,将大大提高程序运行效率,同时可以通过其自身的管理机制来监视数据库连接的数量、使用情况等。

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

    • 连接池的工作原理

    连接池的工作原理主要由三部分组成,分别为连接池的建立、连接池中连接的使用管理、连接池的关闭。

    1. 连接池的建立。一般在系统初始化时,连接池会根据系统配置建立,并在池中创建了几个连接对象,以便使用时能从连接池中获取。连接池中的连接不能随意创建和关闭,这样避免了连接随意建立和关闭造成的系统开销。Java中提供了很多容器类可以方便的构建连接池,例如Vector、Stack等。

    2. 连接池的管理。连接池管理策略是连接池机制的核心,连接池内连接的分配和释放对系统的性能有很大的影响。其管理策略是: 当客户请求数据库连接时,首先查看连接池中是否有空闲连接,如果存在空闲连接,则将连接分配给客户使用;如果没有空闲连接,则查看当前所开的连接数是否已经达到最大连接数,如果没达到就重新创建一个连接给请求的客户;如果达到就按设定的最大等待时间进行等待,如果超出最大等待时间,则抛出异常给客户。

      当客户释放数据库连接时,先判断该连接的引用次数是否超过了规定值,如果超过就从连接池中删除该连接,否则保留为其他客户服务。 该策略保证了数据库连接的有效复用,避免频繁的建立、释放连接所带来的系统资源开销。

    3. 连接池的关闭。当应用程序退出时,关闭连接池中所有的连接,释放连接池相关的资源,该过程正好与创建相反。

    • 常见的连接池

      DBCP: Spring推荐的(Spring框架已经集成DBCP)

      C3P0: Hibernate推荐的(早期)(Hibernate框架已经集成C3P0)

      Hikari:HikariCP可能是目前业内最快的数据库连接池,而且轻量的连接池

      // 1.导包
      // slf4j-api.jar
      // HikariCP.jar
      // 2.创建连接池对象
      HikariDataSource pool = new HikariDataSource();
      // 3.设置连接数据库的参数和连接池本身的属性
      pool.setJdbcUrl(url);
      pool.setUsername(username);
      pool.setPassword(password);
      

      Druid :Druid是阿里巴巴开源的“为监控而生的数据库连接池!”。 性能测试过程略低于HikariCP,但是提供了强大的监

      控和扩展功能,支持psCache。

获取执行对象

执行sql时需要有对应的执行对象,Java中提供了两种类执行sql

  1. Statement这个类会存在sql注入的问题,比如在输入的密码中拼接sql的关键字 or 1=1,以达到通过校验的目的。

    try (
         			Connection connect = JDBCUtil.getConnect();
              Statement statement = connect.createStatement();
       ){
    
         // 定义sql语句
         String sql = "insert into emp(empno,ename,job,mgr,hiredate,sal,comm,deptno) value("+emp.getEmpno()+",'"+emp.getEname()+"','"+emp.getJob()+"',"+emp.getMgr()+","+emp.getHiredate()+","+emp.getSal()+","+emp.getComm()+","+emp.getDeptno()+")";
         // 使用statement对象执行sql语句
         statement.execute(sql);
    
  2. PrepareStatement 这个类使用占位符 ? 替代需要入参的位置,后续将对应位置的值设置好进行执行。与 Statement 的区别是 PrepareStatement 是接收参数前编译sql,而 Statement 是接收参数后编译sql。

    在设置值时,第一个参数是占位符的参数角标,从1开始。

    try(
         			// 1.获取数据库连接
              Connection connect = JDBCUtil.getConnect();
              // 2.建立PreparedStatement对象执行sql
              PreparedStatement preparedStatement = connect.prepareStatement(sql)
       ) {
          // 3.设置sql中占位符的值
          preparedStatement.setString(1, user.getName());
          preparedStatement.setString(2, user.getPwd());
          // 4.执行sql,获得返回集
          resultSet = preparedStatement.executeQuery();
    

获取主键

在创建表时有自增长主键。在添加数据后,之前是可以在数据库表中看见自增的主键的。使用Java代码也可获取对应的自增长主键,后续涉及到多表操作时通常会获取自增长的id值。

获取方式根据执行sql的对象进行区分:

  1. Statement通过 Statement 接口实现:

    int executeUpdate(sql语句,int autoGeneratedKeys) 执行指定语句,通过标识决定是否获取自动生成的主键,标识取值于statement接口中的字段

    ResultSet getGeneratedKeys() 获取返回的自动生成的主键

    @Test
    public void test1() throws SQLException{
    	Connection conn = JDBCUtil2.getConnection();
     	Statement st = conn.createStatement();
     	String sql = "insert into account(name,money) values('刘备',100)";
     	st.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
     	//通过这种方式拿到主键
     	ResultSet rs = st.getGeneratedKeys();
     	while(rs.next()){
     		System.out.println("新增的主键为:"+rs.getLong(1));
     	}
    }
    
  2. PreparedStatement 通过 Connection 接口和 PreparedStatement 接口实现:

    Connection 接口:

    PreparedStatement preparedStatement(sql语句,是否返回id的标识) 创建预编译对象,该对象包含一个是否返回主键的标识

    PreparedStatement 接口:

    ResultSet getGeneratedKeys() 获取ID值

    @Test
    public void test2() throws SQLException{
     	Connection conn = JDBCUtil2.getConnection();
     	String sql = "insert into account(name,money) values('貂蝉',1100)";
     	PreparedStatement st = conn.prepareStatement(sql,Statement.RETURN_GENERATED_KEYS);
     	st.executeUpdate();
     	//通过这种方式拿到主键
     	ResultSet rs = st.getGeneratedKeys();
     	while(rs.next()){
     		System.out.println("新增的主键为:"+rs.getLong(1));
     	}
    }
    

事务

数据库的事务(Transaction)是一种机制、一个操作序列,包含了一组数据库操作命令。事务把所有的命令作为一个整体一起向系统提交或撤销操作请求,即这一组数据库命令,要么同时成功要么同时失败。事务是一个不可分割的工作逻辑单元。

事务四大特征

  • 原子性(Atomicity)事务是不可分割的最小操作单位,要么同时成功,要么同时失败

  • 一致性(Consistency)事务完成时,必须使所有的数据都保持一致状态

    例子:拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性;

  • 隔离性(Isolation)多个事务之间需要相互隔离

  • 持久性(Durability)事务一旦提交或回滚,它对数据库中的数据的改变就是永久的

:在JDBC和mysql中,事务是默认提交的,在执行一个DML/DDL操作的时候,就已经提交事务了。

事务语法

分别通过Mysql与Java中的语法说明:

  1. Mysql中的命令:在Mysql中默认都是自动提交事务,SQL只要一提交,就执行。
# 开启事务
START TRANSACTION;
# 或 
BEGIN;
# 提交事务
commit;
# 回滚事务
rollback;
  1. Java中的 Connection 接口:

    void setAutoCommit(Boolean autoCommit) 设定是否自动提交数据,在执行操作之前;

    autoCommit 表示是否自动提交事务,true表示自动提交事务,false表示手动提交事务。而开启事务需要将该参数设为为false

    void commit() 提交事务,在执行操作之后;

    void rollback() 回滚事务;

    public class JDBCTest {
        public static void main(String[] args) {
            // 1. 注册驱动
            // Class.forName("com.mysql.jdbc.Driver");
            String url = "jdbc:mysql:///java1125";
            String username = "root";
            String password = "123456";
            Connection conn = null;
            Statement stmt = null;
            try {
                // 2. 获取连接:如果连接的是本机mysql并且端口是默认的 3306 可以简化书写
                conn = DriverManager.getConnection(url, username, password);
                // 3. 获取执行sql的对象 Statement
                stmt = conn.createStatement();
                // 4. 定义sql
                String sql1 = "update account set money = money - 500 where id = 1";
                String sql2 = "update account set money = money + 500 where id = 2";
                // ============开启事务==========
                conn.setAutoCommit(false);
                // 5. 执行sql
                int count1 = stmt.executeUpdate(sql1);// 受影响的行数
                // 6. 处理结果
                System.out.println(count1);
                int i = 3 / 0;
                // 5. 执行sql
                int count2 = stmt.executeUpdate(sql2);// 受影响的行数
                // 6. 处理结果
                System.out.println(count2);
                // ============提交事务==========
                // 程序运行到此处,说明没有出现任何问题,则需求提交事务
                conn.commit();
            } catch (Exception e) {
                // ============回滚事务==========
                // 程序在出现异常时会执行到这个地方,此时就需要回滚事务
                try {
                    if(conn!=null){
                        conn.rollback();
                        System.out.println("事务已回滚");
                    }
                } catch (SQLException e1) {
                    e1.printStackTrace();
                }
                e.printStackTrace();
            }
        }
    }