JDBC核心技术

第1章:JDBC概述

1.1 数据的耐久化

  • 耐久化(persistence):把数据保存到可掉电式存储设备中以供之后运用。大多数情况下,特别是企业级运用,数据耐久化意味着将内存中的数据保存到硬盘上加以”固化” ,而耐久化的完结进程大多经过各种联系数据库来完结

  • 耐久化的首要运用是将内存中的数据存储在联系型数据库中,当然也能够存储在磁盘文件、XML数据文件中。

    JDBC核心技术

1.2 Java中的数据存储技能

  • 在Java中,数据库存取技能可分为如下几类:

    • JDBC直接拜访数据库
    • JDO (Java Data Object )技能
    • 第三方O/R东西,如Hibernate, Mybatis 等
  • JDBC是java拜访数据库的基石,JDO、Hibernate、MyBatis等仅仅更好的封装了JDBC。

1.3 JDBC介绍

  • JDBC(Java Database Connectivity)是一个独立于特定数据库办理体系、通用的SQL数据库存取和操作的公共接口(一组API),界说了用来拜访数据库的规范Java类库,(java.sql,javax.sql)运用这些类库能够以一种规范的办法、方便地拜访数据库资源。
  • JDBC为拜访不同的数据库供给了一种一致的途径,为开发者屏蔽了一些细节问题。
  • JDBC的目标是使Java程序员运用JDBC能够衔接任何供给了JDBC驱动程序的数据库体系,这样就使得程序员无需对特定的数据库体系的特点有过多的了解,然后大大简化和加快了开发进程。
  • 假如没有JDBC,那么Java程序拜访数据库时是这样的:
    JDBC核心技术

  • 有了JDBC,Java程序拜访数据库时是这样的:
    JDBC核心技术

  • 总结如下:
    JDBC核心技术

1.4 JDBC体系结构

  • JDBC接口(API)包含两个层次:

    • 面向运用的API:Java API,笼统接口,供运用程序开发人员运用(衔接数据库,履行SQL句子,取得成果)。
    • 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序用。

JDBC是sun公司供给一套用于数据库操作的接口,java程序员只需求面向这套接口编程即可。

不同的数据库厂商,需求针对这套接口,供给不同完结。不同的完结的调集,即为不同数据库的驱动。 ————面向接口编程

1.5 JDBC程序编写过程

JDBC核心技术

弥补:ODBC(Open Database Connectivity,开放式数据库衔接),是微软在Windows渠道下推出的。运用者在程序中只需求调用ODBC API,由 ODBC 驱动程序将调用转化成为对特定的数据库的调用恳求。

第2章:获取数据库衔接

2.1 要素一:Driver接口完结类

2.1.1 Driver接口介绍

  • java.sql.Driver 接口是一切 JDBC 驱动程序需求完结的接口。这个接口是供给给数据库厂商运用的,不同数据库厂商供给不同的完结。

  • 在程序中不需求直接去拜访完结了 Driver 接口的类,而是由驱动程序办理器类(java.sql.DriverManager)去调用这些Driver完结。

    • Oracle的驱动:oracle.jdbc.driver.OracleDriver
    • mySql的驱动: com.mysql.jdbc.Driver
      JDBC核心技术
      JDBC核心技术
  • 将上述jar包拷贝到Java工程的一个目录中,习惯上新建一个lib文件夹。

    JDBC核心技术

在驱动jar上右键–>Build Path–>Add to Build Path

JDBC核心技术

留意:假如是Dynamic Web Project(动态的web项目)话,则是把驱动jar放到WebContent(有的开发东西叫WebRoot)目录中的WEB-INF目录中的lib目录下即可

JDBC核心技术

2.1.2 加载与注册JDBC驱动

  • 加载驱动:加载 JDBC 驱动需调用 Class 类的静态办法 forName(),向其传递要加载的 JDBC 驱动的类名

    • Class.forName(“com.mysql.jdbc.Driver”);
  • 注册驱动:DriverManager 类是驱动程序办理器类,负责办理驱动程序

    • 运用DriverManager.registerDriver(com.mysql.jdbc.Driver)来注册驱动

    • 一般不必显式调用 DriverManager 类的 registerDriver() 办法来注册驱动程序类的实例,由于 Driver 接口的驱动程序类包含了静态代码块,在这个静态代码块中,会调用 DriverManager.registerDriver() 办法来注册自身的一个实例。下图是MySQL的Driver完结类的源码:

      JDBC核心技术

2.2 要素二:URL

  • JDBC URL 用于标识一个被注册的驱动程序,驱动程序办理器经过这个 URL 挑选正确的驱动程序,然后树立到数据库的衔接。

  • JDBC URL的规范由三部分组成,各部分间用冒号分隔。

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

JDBC核心技术

  • 几种常用数据库的 JDBC URL

    • MySQL的衔接URL编写办法:

      • jdbc:mysql://主机称号:mysql服务端口号/数据库称号?参数=值&参数=值
      • jdbc:mysql://localhost:3306/atguigu
      • jdbc:mysql://localhost:3306/atguigu ?useUnicode=true&characterEncoding=utf8(假如JDBC程序与服务器端的字符集不一致,会导致乱码,那么能够经过参数指定服务器端的字符集)
      • jdbc:mysql://localhost:3306/atguigu?user=root&password=123456
    • Oracle 9i的衔接URL编写办法:

      • jdbc:oracle:thin:@主机称号:oracle服务端口号:数据库称号
      • jdbc:oracle:thin:@localhost:1521:atguigu
    • SQLServer的衔接URL编写办法:

      • jdbc:sqlserver://主机称号:sqlserver服务端口号:DatabaseName=数据库称号
      • jdbc:sqlserver://localhost:1433:DatabaseName=atguigu

2.3 要素三:用户名和暗码

  • user,password能够用“特点名=特点值”办法告诉数据库
  • 能够调用 DriverManager 类的 getConnection() 办法树立到数据库的衔接

2.4 数据库衔接办法举例

2.4.1 衔接办法一

    @Test
  public void testConnection1() {
    try {
      //1.供给java.sql.Driver接口完结类的目标
      Driver driver = null;
      driver = new com.mysql.jdbc.Driver();
​
      //2.供给url,指明具体操作的数据
      String url = "jdbc:mysql://localhost:3306/test";
​
      //3.供给Properties的目标,指明用户名和暗码
      Properties info = new Properties();
      info.setProperty("user", "root");
      info.setProperty("password", "abc123");
​
      //4.调用driver的connect(),获取衔接
      Connection conn = driver.connect(url, info);
      System.out.println(conn);
     } catch (SQLException e) {
      e.printStackTrace();
     }
   }

阐明:上述代码中显式呈现了第三方数据库的API

2.4.2 衔接办法二

    @Test
  public void testConnection2() {
    try {
      //1.实例化Driver
      String className = "com.mysql.jdbc.Driver";
      Class clazz = Class.forName(className);
      Driver driver = (Driver) clazz.newInstance();
​
      //2.供给url,指明具体操作的数据
      String url = "jdbc:mysql://localhost:3306/test";
​
      //3.供给Properties的目标,指明用户名和暗码
      Properties info = new Properties();
      info.setProperty("user", "root");
      info.setProperty("password", "abc123");
​
      //4.调用driver的connect(),获取衔接
      Connection conn = driver.connect(url, info);
      System.out.println(conn);
​
     } catch (Exception e) {
      e.printStackTrace();
     }
   }

阐明:相较于办法一,这里运用反射实例化Driver,不在代码中体现第三方数据库的API。体现了面向接口编程思维。

2.4.3 衔接办法三

    @Test
  public void testConnection3() {
    try {
      //1.数据库衔接的4个根本要素:
      String url = "jdbc:mysql://localhost:3306/test";
      String user = "root";
      String password = "abc123";
      String driverName = "com.mysql.jdbc.Driver";
​
      //2.实例化Driver
      Class clazz = Class.forName(driverName);
      Driver driver = (Driver) clazz.newInstance();
      //3.注册驱动
      DriverManager.registerDriver(driver);
      //4.获取衔接
      Connection conn = DriverManager.getConnection(url, user, password);
      System.out.println(conn);
     } catch (Exception e) {
      e.printStackTrace();
     }
​
   }

阐明:运用DriverManager完结数据库的衔接。体会获取衔接必要的4个根本要素。

2.4.4 衔接办法四

    @Test
  public void testConnection4() {
    try {
      //1.数据库衔接的4个根本要素:
      String url = "jdbc:mysql://localhost:3306/test";
      String user = "root";
      String password = "abc123";
      String driverName = "com.mysql.jdbc.Driver";
​
      //2.加载驱动 (①实例化Driver ②注册驱动)
      Class.forName(driverName);
​
​
      //Driver driver = (Driver) clazz.newInstance();
      //3.注册驱动
      //DriverManager.registerDriver(driver);
      /*
      能够注释掉上述代码的原因,是由于在mysql的Driver类中声明有:
      static {
        try {
          DriverManager.registerDriver(new Driver());
        } catch (SQLException var1) {
          throw new RuntimeException("Can't register driver!");
        }
      }
​
       */
​
​
      //3.获取衔接
      Connection conn = DriverManager.getConnection(url, user, password);
      System.out.println(conn);
     } catch (Exception e) {
      e.printStackTrace();
     }
​
   }

阐明:不必显式的注册驱动了。由于在DriverManager的源码中现已存在静态代码块,完结了驱动的注册。

2.4.5 衔接办法五(最终版)

    @Test
  public void testConnection5() throws Exception {
       //1.加载装备文件
    InputStream is = ConnectionTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
    Properties pros = new Properties();
    pros.load(is);
    
    //2.读取装备信息
    String user = pros.getProperty("user");
    String password = pros.getProperty("password");
    String url = pros.getProperty("url");
    String driverClass = pros.getProperty("driverClass");
​
    //3.加载驱动
    Class.forName(driverClass);
​
    //4.获取衔接
    Connection conn = DriverManager.getConnection(url,user,password);
    System.out.println(conn);
​
   }

其间,装备文件声明在工程的src目录下:【jdbc.properties】

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

阐明:运用装备文件的办法保存装备信息,在代码中加载装备文件

运用装备文件的长处:

①完结了代码和数据的别离,假如需求修正装备信息,直接在装备文件中修正,不需求深入代码 ②假如修正了装备信息,省去从头编译的进程。

第3章:运用PreparedStatement完结CRUD操作

3.1 操作和拜访数据库

  • 数据库衔接被用于向数据库服务器发送指令和 SQL 句子,并承受数据库服务器回来的成果。其实一个数据库衔接便是一个Socket衔接。

  • 在 java.sql 包中有 3 个接口别离界说了对数据库的调用的不同办法:

    • Statement:用于履行静态 SQL 句子并回来它所生成成果的目标。
    • PrepatedStatement:SQL 句子被预编译并存储在此目标中,能够运用此目标屡次高效地履行该句子。
    • CallableStatement:用于履行 SQL 存储进程

JDBC核心技术

3.2 运用Statement操作数据表的弊端

  • 经过调用 Connection 目标的 createStatement() 办法创立该目标。该目标用于履行静态的 SQL 句子,而且回来履行成果。

  • Statement 接口中界说了下列办法用于履行 SQL 句子:

    int excuteUpdate(String sql):履行更新操作INSERTUPDATEDELETE
    ResultSet executeQuery(String sql):履行查询操作SELECT
    
  • 可是运用Statement操作数据表存在弊端:

    • 问题一:存在拼串操作,繁琐
    • 问题二:存在SQL注入问题
  • SQL 注入是运用某些体系没有对用户输入的数据进行充沛的检查,而在用户输入数据中注入非法的 SQL 句子段或指令(如:SELECT user, password FROM user_table WHERE user=’a’ OR 1 = ‘ AND password = ‘ OR ‘1’ = ‘1’) ,然后运用体系的 SQL 引擎完结恶意行为的做法。

  • 关于 Java 而言,要防备 SQL 注入,只要用 PreparedStatement(从Statement扩展而来) 替代 Statement 就能够了。

  • 代码演示:

public class StatementTest {
​
    // 运用Statement的弊端:需求拼写sql句子,而且存在SQL注入的问题
    @Test
    public void testLogin() {
        Scanner scan = new Scanner(System.in);
​
        System.out.print("用户名:");
        String userName = scan.nextLine();
        System.out.print("密  码:");
        String password = scan.nextLine();
​
        // SELECT user,password FROM user_table WHERE USER = '1' or ' AND PASSWORD = '='1' or '1' = '1';
        String sql = "SELECT user,password FROM user_table WHERE USER = '" + userName + "' AND PASSWORD = '" + password
                + "'";
        User user = get(sql, User.class);
        if (user != null) {
            System.out.println("登陆成功!");
        } else {
            System.out.println("用户名或暗码错误!");
        }
    }
​
    // 运用Statement完结对数据表的查询操作
    public <T> T get(String sql, Class<T> clazz) {
        T t = null;
​
        Connection conn = null;
        Statement st = null;
        ResultSet rs = null;
        try {
            // 1.加载装备文件
            InputStream is = StatementTest.class.getClassLoader().getResourceAsStream("jdbc.properties");
            Properties pros = new Properties();
            pros.load(is);
​
            // 2.读取装备信息
            String user = pros.getProperty("user");
            String password = pros.getProperty("password");
            String url = pros.getProperty("url");
            String driverClass = pros.getProperty("driverClass");
​
            // 3.加载驱动
            Class.forName(driverClass);
​
            // 4.获取衔接
            conn = DriverManager.getConnection(url, user, password);st = conn.createStatement();rs = st.executeQuery(sql);
​
            // 获取成果集的元数据
            ResultSetMetaData rsmd = rs.getMetaData();
​
            // 获取成果集的列数
            int columnCount = rsmd.getColumnCount();
​
            if (rs.next()) {
​
                t = clazz.newInstance();
​
                for (int i = 0; i < columnCount; i++) {
                    // //1. 获取列的称号
                    // String columnName = rsmd.getColumnName(i+1);
​
                    // 1. 获取列的别号
                    String columnName = rsmd.getColumnLabel(i + 1);
​
                    // 2. 依据列名获取对应数据表中的数据
                    Object columnVal = rs.getObject(columnName);
​
                    // 3. 将数据表中得到的数据,封装进目标
                    Field field = clazz.getDeclaredField(columnName);
                    field.setAccessible(true);
                    field.set(t, columnVal);
                }
                return t;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 封闭资源
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (st != null) {
                try {
                    st.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
​
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
​
        return null;
    }
}

综上:

JDBC核心技术

3.3 PreparedStatement的运用

3.3.1 PreparedStatement介绍

  • 能够经过调用 Connection 目标的 preparedStatement(String sql) 办法获取 PreparedStatement 目标
  • PreparedStatement 接口是 Statement 的子接口,它表明一条预编译过的 SQL 句子
  • PreparedStatement 目标所代表的 SQL 句子中的参数用问号(?)来表明,调用 PreparedStatement 目标的 setXxx() 办法来设置这些参数. setXxx() 办法有两个参数,榜首个参数是要设置的 SQL 句子中的参数的索引(从 1 开端),第二个是设置的 SQL 句子中的参数的值

3.3.2 PreparedStatement vs Statement

  • 代码的可读性和可保护性。

  • PreparedStatement 能最大或许提高功能:

    • DBServer会对预编译句子供给功能优化。由于预编译句子有或许被重复调用,所以句子在被DBServer的编译器编译后的履行代码被缓存下来,那么下次调用时只要是相同的预编译句子就不需求编译,只要将参数直接传入编译过的句子履行代码中就会得到履行。
    • 在statement句子中,即使是相同操作但由于数据内容不相同,所以整个句子本身不能匹配,没有缓存句子的含义.事实是没有数据库会对一般句子编译后的履行代码缓存。这样每履行一次都要对传入的句子编译一次。
    • (语法检查,语义检查,翻译成二进制指令,缓存)
  • PreparedStatement 能够防止 SQL 注入

3.3.3 Java与SQL对应数据类型转化表

Java类型 SQL类型
boolean BIT
byte TINYINT
short SMALLINT
int INTEGER
long BIGINT
String CHAR,VARCHAR,LONGVARCHAR
byte array BINARY , VAR BINARY
java.sql.Date DATE
java.sql.Time TIME
java.sql.Timestamp TIMESTAMP

3.3.4 运用PreparedStatement完结增、删、改操作

	//通用的增、删、改操作(体现一:增、删、改 ; 体现二:针关于不同的表)
	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);
		}
	}

3.3.5 运用PreparedStatement完结查询操作

	// 通用的针关于不同表的查询:回来一个目标 (version 1.0)
	public <T> T getInstance(Class<T> clazz, String sql, Object... args) {
		Connection conn = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			// 1.获取数据库衔接
			conn = JDBCUtils.getConnection();
			// 2.预编译sql句子,得到PreparedStatement目标
			ps = conn.prepareStatement(sql);
			// 3.填充占位符
			for (int i = 0; i < args.length; i++) {
				ps.setObject(i + 1, args[i]);
			}
			// 4.履行executeQuery(),得到成果集:ResultSet
			rs = ps.executeQuery();
			// 5.得到成果集的元数据:ResultSetMetaData
			ResultSetMetaData rsmd = rs.getMetaData();
			// 6.1经过ResultSetMetaData得到columnCount,columnLabel;经过ResultSet得到列值
			int columnCount = rsmd.getColumnCount();
			if (rs.next()) {
				T t = clazz.newInstance();
				for (int i = 0; i < columnCount; i++) {// 遍历每一个列
					// 获取列值
					Object columnVal = rs.getObject(i + 1);
					// 获取列的别号:列的别号,运用类的特点名充当
					String columnLabel = rsmd.getColumnLabel(i + 1);
					// 6.2运用反射,给目标的相应特点赋值
					Field field = clazz.getDeclaredField(columnLabel);
					field.setAccessible(true);
					field.set(t, columnVal);
				}
				return t;
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			// 7.封闭资源
			JDBCUtils.closeResource(conn, ps, rs);
		}
		return null;
	}

阐明:运用PreparedStatement完结的查询操作能够替换Statement完结的查询操作,处理Statement拼串和SQL注入问题。

3.4 ResultSet与ResultSetMetaData

3.4.1 ResultSet

  • 查询需求调用PreparedStatement 的 executeQuery() 办法,查询成果是一个ResultSet 目标

  • ResultSet 目标以逻辑表格的方式封装了履行数据库操作的成果集,ResultSet 接口由数据库厂商供给完结

  • ResultSet 回来的实际上便是一张数据表。有一个指针指向数据表的榜首条记载的前面。

  • ResultSet 目标保护了一个指向当时数据行的游标,初始的时分,游标在榜首行之前,能够经过 ResultSet 目标的 next() 办法移动到下一行。调用 next()办法检测下一行是否有用。若有用,该办法回来 true,且指针下移。相当于Iterator目标的 hasNext() 和 next() 办法的结合体。

  • 当指针指向一行时, 能够经过调用 getXxx(int index) 或 getXxx(int columnName) 获取每一列的值。

    • 例如: getInt(1), getString(“name”)
    • 留意:Java与数据库交互涉及到的相关Java API中的索引都从1开端。
  • ResultSet 接口的常用办法:

    • boolean next()
    • getString()
    • JDBC核心技术

3.4.2 ResultSetMetaData

  • 可用于获取关于 ResultSet 目标中列的类型和特点信息的目标

  • ResultSetMetaData meta = rs.getMetaData();

    • getColumnName(int column):获取指定列的称号
    • getColumnLabel(int column):获取指定列的别号
    • getColumnCount():回来当时 ResultSet 目标中的列数。
    • getColumnTypeName(int column):检索指定列的数据库特定的类型称号。
    • getColumnDisplaySize(int column):指示指定列的最大规范宽度,以字符为单位。
    • isNullable(int column):指示指定列中的值是否能够为 null。
    • isAutoIncrement(int column):指示是否主动为指定列进行编号,这样这些列仍然是只读的。

JDBC核心技术

问题1:得到成果集后, 怎么知道该成果会集有哪些列 ? 列名是什么?

需求运用一个描述 ResultSet 的目标, 即 ResultSetMetaData

问题2:关于ResultSetMetaData

  1. 怎么获取 ResultSetMetaData: 调用 ResultSet 的 getMetaData() 办法即可
  2. 获取 ResultSet 中有多少列:调用 ResultSetMetaData 的 getColumnCount() 办法
  3. 获取 ResultSet 每一列的列的别号是什么:调用 ResultSetMetaData 的getColumnLabel() 办法

JDBC核心技术

3.5 资源的开释

  • 开释ResultSet, Statement,Connection。
  • 数据库衔接(Connection)是十分稀有的资源,用完后有必要马上开释,假如Connection不能及时正确的封闭将导致体系宕机。Connection的运用原则是尽量晚创立,尽量早的开释。
  • 能够在finally中封闭,确保及时其他代码呈现反常,资源也必定能被封闭。

3.6 JDBC API小结

  • 两种思维

    • 面向接口编程的思维

    • ORM思维(object relational mapping)

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

      • 表中的一条记载对应java类的一个目标

      • 表中的一个字段对应java类的一个特点

    sql是需求结合列名和表的特点名来写。留意起别号。

  • 两种技能

    • JDBC成果集的元数据:ResultSetMetaData

      • 获取列数:getColumnCount()
      • 获取列的别号:getColumnLabel()
    • 经过反射,创立指定类的目标,获取指定的特点并赋值


章节操练

操练题1:从控制台向数据库的表customers中刺进一条数据,表结构如下:

JDBC核心技术

操练题2:创立数据库表 examstudent,表结构如下:

JDBC核心技术
向数据表中添加如下数据:
JDBC核心技术

代码完结1:刺进一个新的student 信息

请输入考生的具体信息

Type: IDCard: ExamCard: StudentName: Location: Grade:

信息录入成功!

代码完结2:在 eclipse中树立 java 程序:输入身份证号或准考证号能够查询到学生的根本信息。成果如下:

JDBC核心技术

代码完结3:完结学生信息的删除功用

JDBC核心技术


第4章 操作BLOB类型字段

4.1 MySQL BLOB类型

  • MySQL中,BLOB是一个二进制大型目标,是一个能够存储很多数据的容器,它能包容不同大小的数据。

  • 刺进BLOB类型的数据有必要运用PreparedStatement,由于BLOB类型的数据无法运用字符串拼接写的。

  • MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是同等的)

    JDBC核心技术

  • 实际运用中依据需求存入的数据大小界说不同的BLOB类型。

  • 需求留意的是:假如存储的文件过大,数据库的功能会下降。

  • 假如在指定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找my.ini文件加上如下的装备参数: max_allowed_packet=16M。一起留意:修正了my.ini文件之后,需求从头启动mysql服务。

4.2 向数据表中刺进大数据类型

//获取衔接
Connection conn = JDBCUtils.getConnection();
String sql = "insert into customers(name,email,birth,photo)values(?,?,?,?)";
PreparedStatement ps = conn.prepareStatement(sql);
// 填充占位符
ps.setString(1, "徐海强");
ps.setString(2, "xhq@126.com");
ps.setDate(3, new Date(new java.util.Date().getTime()));
// 操作Blob类型的变量
FileInputStream fis = new FileInputStream("xhq.png");
ps.setBlob(4, fis);
//履行
ps.execute();
fis.close();
JDBCUtils.closeResource(conn, ps);

4.3 修正数据表中的Blob类型字段

Connection conn = JDBCUtils.getConnection();
String sql = "update customers set photo = ? where id = ?";
PreparedStatement ps = conn.prepareStatement(sql);
// 填充占位符
// 操作Blob类型的变量
FileInputStream fis = new FileInputStream("coffee.png");
ps.setBlob(1, fis);
ps.setInt(2, 25);
ps.execute();
fis.close();
JDBCUtils.closeResource(conn, ps);

4.4 从数据表中读取大数据类型

String sql = "SELECT id, name, email, birth, photo FROM customer WHERE id = ?";
conn = getConnection();
ps = conn.prepareStatement(sql);
ps.setInt(1, 8);
rs = ps.executeQuery();
if(rs.next()){
	Integer id = rs.getInt(1);
    String name = rs.getString(2);
	String email = rs.getString(3);
    Date birth = rs.getDate(4);
	Customer cust = new Customer(id, name, email, birth);
    System.out.println(cust); 
    //读取Blob类型的字段
	Blob photo = rs.getBlob(5);
	InputStream is = photo.getBinaryStream();
	OutputStream os = new FileOutputStream("c.jpg");
	byte [] buffer = new byte[1024];
	int len = 0;
	while((len = is.read(buffer)) != -1){
		os.write(buffer, 0, len);
	}
    JDBCUtils.closeResource(conn, ps, rs);
	if(is != null){
		is.close();
	}
	if(os !=  null){
		os.close();
	}
}

第5章 批量刺进

5.1 批量履行SQL句子

当需求成批刺进或者更新记载时,能够选用Java的批量更新机制,这一机制答应多条句子一次性提交给数据库批量处理。一般情况下比独自提交处理更有功率

JDBC的批量处理句子包含下面三个办法:

  • addBatch(String):添加需求批量处理的SQL句子或是参数;
  • executeBatch():履行批量处理句子;
  • clearBatch():清空缓存的数据

一般咱们会遇到两种批量履行SQL句子的情况:

  • 多条SQL句子的批量处理;
  • 一个SQL句子的批量传参;

5.2 高效的批量刺进

举例:向数据表中刺进20000条数据

  • 数据库中供给一个goods表。创立如下:
CREATE TABLE goods(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(20)
);

5.2.1 完结层次一:运用Statement

Connection conn = JDBCUtils.getConnection();
Statement st = conn.createStatement();
for(int i = 1;i <= 20000;i++){
	String sql = "insert into goods(name) values('name_' + "+ i +")";
	st.executeUpdate(sql);
}

5.2.2 完结层次二:运用PreparedStatement

long start = System.currentTimeMillis();
Connection conn = JDBCUtils.getConnection();
String sql = "insert into goods(name)values(?)";
PreparedStatement ps = conn.prepareStatement(sql);
for(int i = 1;i <= 20000;i++){
	ps.setString(1, "name_" + i);
	ps.executeUpdate();
}
long end = System.currentTimeMillis();
System.out.println("花费的时刻为:" + (end - start));//82340
JDBCUtils.closeResource(conn, ps);

5.2.3 完结层次三

/*
 * 修正1: 运用 addBatch() / executeBatch() / clearBatch()
 * 修正2:mysql服务器默许是封闭批处理的,咱们需求经过一个参数,让mysql敞开批处理的支撑。
 * 		 ?rewriteBatchedStatements=true 写在装备文件的url后面
 * 修正3:运用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
 * 
 */
@Test
public void testInsert1() throws Exception{
	long start = System.currentTimeMillis();
	Connection conn = JDBCUtils.getConnection();
	String sql = "insert into goods(name)values(?)";
	PreparedStatement ps = conn.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));//20000条:625                                                                         //1000000条:14733  
	JDBCUtils.closeResource(conn, ps);
}

5.2.4 完结层次四

/*
* 层次四:在层次三的基础上操作
* 运用Connection 的 setAutoCommit(false)  /  commit()
*/
@Test
public void testInsert2() throws Exception{
	long start = System.currentTimeMillis();
	Connection conn = JDBCUtils.getConnection();
	//1.设置为不主动提交数据
	conn.setAutoCommit(false);
	String sql = "insert into goods(name)values(?)";
	PreparedStatement ps = conn.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();
		}
	}
	//2.提交数据
	conn.commit();
	long end = System.currentTimeMillis();
	System.out.println("花费的时刻为:" + (end - start));//1000000条:4978 
	JDBCUtils.closeResource(conn, ps);
}

第6章: 数据库业务

6.1 数据库业务介绍

  • 业务:一组逻辑操作单元,使数据从一种状况变换到另一种状况。
  • 业务处理(业务操作): 确保一切业务都作为一个作业单元来履行,即使呈现了毛病,都不能改动这种履行办法。当在一个业务中履行多个操作时,要么一切的业务都被提交(commit) ,那么这些修正就永久地保存下来;要么数据库办理体系将扔掉所作的一切修正,整个业务回滚(rollback) 到开始状况。
  • 为确保数据库中数据的一致性,数据的操纵应当是离散的成组的逻辑单元:当它全部完结时,数据的一致功能够坚持,而当这个单元中的一部分操作失利,整个业务应全部视为错误,一切从起始点以后的操作应全部回退到开端状况。

6.2 JDBC业务处理

  • 数据一旦提交,就不行回滚。

  • 数据什么时分意味着提交?

    • 当一个衔接目标被创立时,默许情况下是主动提交业务:每次履行一个 SQL 句子时,假如履行成功,就会向数据库主动提交,而不能回滚。
    • 封闭数据库衔接,数据就会主动的提交。 假如多个操作,每个操作运用的是自己独自的衔接,则无法确保业务。即同一个业务的多个操作有必要在同一个衔接下。
  • JDBC程序中为了让多个 SQL 句子作为一个业务履行:

    • 调用 Connection 目标的 setAutoCommit(false); 以取消主动提交业务

    • 在一切的 SQL 句子都成功履行后,调用 commit(); 办法提交业务

    • 在呈现反常时,调用 rollback(); 办法回滚业务

    若此刻 Connection 没有被封闭,还或许被重复运用,则需求康复其主动提交状况 setAutoCommit(true)。尤其是在运用数据库衔接池技能时,履行close()办法前,主张康复主动提交状况。

【案例:用户AA向用户BB转账100】

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); 
    }  
}

其间,对数据库操作的办法为:

//运用业务以后的通用的增修改操作(version 2.0)
public void update(Connection conn ,String sql, Object... args) {
	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);
	}
}

6.3 业务的ACID特点

  1. 原子性(Atomicity) 原子性是指业务是一个不行分割的作业单位,业务中的操作要么都发生,要么都不发生。
  2. 一致性(Consistency) 业务有必要使数据库从一个一致性状况变换到别的一个一致性状况。
  3. 阻隔性(Isolation) 业务的阻隔性是指一个业务的履行不能被其他业务搅扰,即一个业务内部的操作及运用的数据对并发的其他业务是阻隔的,并发履行的各个业务之间不能互相搅扰。
  4. 耐久性(Durability) 耐久性是指一个业务一旦被提交,它对数据库中数据的改动便是永久性的,接下来的其他操作和数据库毛病不应该对其有任何影响。

6.3.1 数据库的并发问题

  • 关于一起运转的多个业务, 当这些业务拜访数据库中相同的数据时, 假如没有采取必要的阻隔机制, 就会导致各种并发问题:

    • 脏读: 关于两个业务 T1, T2, T1 读取了现已被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容便是暂时且无效的。
    • 不行重复读: 关于两个业务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
    • 幻读: 关于两个业务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中刺进了一些新的行。之后, 假如 T1 再次读取同一个表, 就会多出几行。
  • 数据库业务的阻隔性: 数据库体系有必要具有阻隔并发运转各个业务的能力, 使它们不会相互影响, 防止各种并发问题。

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

6.3.2 四种阻隔等级

  • 数据库供给的4种业务阻隔等级:

JDBC核心技术

  • Oracle 支撑的 2 种业务阻隔等级:READ COMMITED, SERIALIZABLE。 Oracle 默许的业务阻隔等级为: READ COMMITED
  • Mysql 支撑 4 种业务阻隔等级。Mysql 默许的业务阻隔等级为: REPEATABLE READ。

6.3.3 在MySql中设置阻隔等级

  • 每启动一个 mysql 程序, 就会取得一个独自的数据库衔接. 每个数据库衔接都有一个全局变量 @@tx_isolation, 表明当时的业务阻隔等级。

  • 检查当时的阻隔等级:

    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'; 
      

第7章:DAO及相关完结类

  • DAO:Data Access Object拜访数据信息的类和接口,包含了对数据的CRUD(Create、Retrival、Update、Delete),而不包含任何业务相关的信息。有时也称作:BaseDAO
  • 作用:为了完结功用的模块化,更有利于代码的保护和升级。
  • 下面是尚硅谷JavaWeb阶段书城项目中DAO运用的体现:

JDBC核心技术

  • 层次结构:
    JDBC核心技术

【BaseDAO.java】

package com.atguigu.bookstore.dao;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.apache.commons.dbutils.handlers.ScalarHandler;
/**
 * 界说一个用来被承继的对数据库进行根本操作的Dao
 * 
 * @author HanYanBing
 *
 * @param <T>
 */
public abstract class BaseDao<T> {
	private QueryRunner queryRunner = new QueryRunner();
	// 界说一个变量来接收泛型的类型
	private Class<T> type;
	// 获取T的Class目标,获取泛型的类型,泛型是在被子类承继时才确定
	public BaseDao() {
		// 获取子类的类型
		Class clazz = this.getClass();
		// 获取父类的类型
		// getGenericSuperclass()用来获取当时类的父类的类型
		// ParameterizedType表明的是带泛型的类型
		ParameterizedType parameterizedType = (ParameterizedType) clazz.getGenericSuperclass();
		// 获取具体的泛型类型 getActualTypeArguments获取具体的泛型的类型
		// 这个办法会回来一个Type的数组
		Type[] types = parameterizedType.getActualTypeArguments();
		// 获取具体的泛型的类型
		this.type = (Class<T>) types[0];
	}
	/**
	 * 通用的增修改操作
	 * 
	 * @param sql
	 * @param params
	 * @return
	 */
	public int update(Connection conn,String sql, Object... params) {
		int count = 0;
		try {
			count = queryRunner.update(conn, sql, params);
		} catch (SQLException e) {
			e.printStackTrace();
		} 
		return count;
	}
	/**
	 * 获取一个目标
	 * 
	 * @param sql
	 * @param params
	 * @return
	 */
	public T getBean(Connection conn,String sql, Object... params) {
		T t = null;
		try {
			t = queryRunner.query(conn, sql, new BeanHandler<T>(type), params);
		} catch (SQLException e) {
			e.printStackTrace();
		} 
		return t;
	}
	/**
	 * 获取一切目标
	 * 
	 * @param sql
	 * @param params
	 * @return
	 */
	public List<T> getBeanList(Connection conn,String sql, Object... params) {
		List<T> list = null;
		try {
			list = queryRunner.query(conn, sql, new BeanListHandler<T>(type), params);
		} catch (SQLException e) {
			e.printStackTrace();
		} 
		return list;
	}
	/**
	 * 获取一个但一值得办法,专门用来履行像 select count(*)...这样的sql句子
	 * 
	 * @param sql
	 * @param params
	 * @return
	 */
	public Object getValue(Connection conn,String sql, Object... params) {
		Object count = null;
		try {
			// 调用queryRunner的query办法获取一个单一的值
			count = queryRunner.query(conn, sql, new ScalarHandler<>(), params);
		} catch (SQLException e) {
			e.printStackTrace();
		} 
		return count;
	}
}

【BookDAO.java】

package com.atguigu.bookstore.dao;
import java.sql.Connection;
import java.util.List;
import com.atguigu.bookstore.beans.Book;
import com.atguigu.bookstore.beans.Page;
public interface BookDao {
	/**
	 * 从数据库中查询出一切的记载
	 * 
	 * @return
	 */
	List<Book> getBooks(Connection conn);
	/**
	 * 向数据库中刺进一条记载
	 * 
	 * @param book
	 */
	void saveBook(Connection conn,Book book);
	/**
	 * 从数据库中依据图书的id删除一条记载
	 * 
	 * @param bookId
	 */
	void deleteBookById(Connection conn,String bookId);
	/**
	 * 依据图书的id从数据库中查询出一条记载
	 * 
	 * @param bookId
	 * @return
	 */
	Book getBookById(Connection conn,String bookId);
	/**
	 * 依据图书的id从数据库中更新一条记载
	 * 
	 * @param book
	 */
	void updateBook(Connection conn,Book book);
	/**
	 * 获取带分页的图书信息
	 * 
	 * @param page:是只包含了用户输入的pageNo特点的page目标
	 * @return 回来的Page目标是包含了一切特点的Page目标
	 */
	Page<Book> getPageBooks(Connection conn,Page<Book> page);
	/**
	 * 获取带分页和价格范围的图书信息
	 * 
	 * @param page:是只包含了用户输入的pageNo特点的page目标
	 * @return 回来的Page目标是包含了一切特点的Page目标
	 */
	Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice);
}

【UserDAO.java】

package com.atguigu.bookstore.dao;
import java.sql.Connection;
import com.atguigu.bookstore.beans.User;
public interface UserDao {
	/**
	 * 依据User目标中的用户名和暗码从数据库中获取一条记载
	 * 
	 * @param user
	 * @return User 数据库中有记载 null 数据库中无此记载
	 */
	User getUser(Connection conn,User user);
	/**
	 * 依据User目标中的用户名从数据库中获取一条记载
	 * 
	 * @param user
	 * @return true 数据库中有记载 false 数据库中无此记载
	 */
	boolean checkUsername(Connection conn,User user);
	/**
	 * 向数据库中刺进User目标
	 * 
	 * @param user
	 */
	void saveUser(Connection conn,User user);
}

【BookDaoImpl.java】

package com.atguigu.bookstore.dao.impl;
import java.sql.Connection;
import java.util.List;
import com.atguigu.bookstore.beans.Book;
import com.atguigu.bookstore.beans.Page;
import com.atguigu.bookstore.dao.BaseDao;
import com.atguigu.bookstore.dao.BookDao;
public class BookDaoImpl extends BaseDao<Book> implements BookDao {
	@Override
	public List<Book> getBooks(Connection conn) {
		// 调用BaseDao中得到一个List的办法
		List<Book> beanList = null;
		// 写sql句子
		String sql = "select id,title,author,price,sales,stock,img_path imgPath from books";
		beanList = getBeanList(conn,sql);
		return beanList;
	}
	@Override
	public void saveBook(Connection conn,Book book) {
		// 写sql句子
		String sql = "insert into books(title,author,price,sales,stock,img_path) values(?,?,?,?,?,?)";
		// 调用BaseDao中通用的增修改的办法
		update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(),book.getImgPath());
	}
	@Override
	public void deleteBookById(Connection conn,String bookId) {
		// 写sql句子
		String sql = "DELETE FROM books WHERE id = ?";
		// 调用BaseDao中通用增修改的办法
		update(conn,sql, bookId);
	}
	@Override
	public Book getBookById(Connection conn,String bookId) {
		// 调用BaseDao中获取一个目标的办法
		Book book = null;
		// 写sql句子
		String sql = "select id,title,author,price,sales,stock,img_path imgPath from books where id = ?";
		book = getBean(conn,sql, bookId);
		return book;
	}
	@Override
	public void updateBook(Connection conn,Book book) {
		// 写sql句子
		String sql = "update books set title = ? , author = ? , price = ? , sales = ? , stock = ? where id = ?";
		// 调用BaseDao中通用的增修改的办法
		update(conn,sql, book.getTitle(), book.getAuthor(), book.getPrice(), book.getSales(), book.getStock(), book.getId());
	}
	@Override
	public Page<Book> getPageBooks(Connection conn,Page<Book> page) {
		// 获取数据库中图书的总记载数
		String sql = "select count(*) from books";
		// 调用BaseDao中获取一个单一值的办法
		long totalRecord = (long) getValue(conn,sql);
		// 将总记载数设置都page目标中
		page.setTotalRecord((int) totalRecord);
		// 获取当时页中的记载寄存的List
		String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books limit ?,?";
		// 调用BaseDao中获取一个调集的办法
		List<Book> beanList = getBeanList(conn,sql2, (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE);
		// 将这个List设置到page目标中
		page.setList(beanList);
		return page;
	}
	@Override
	public Page<Book> getPageBooksByPrice(Connection conn,Page<Book> page, double minPrice, double maxPrice) {
		// 获取数据库中图书的总记载数
		String sql = "select count(*) from books where price between ? and ?";
		// 调用BaseDao中获取一个单一值的办法
		long totalRecord = (long) getValue(conn,sql,minPrice,maxPrice);
		// 将总记载数设置都page目标中
		page.setTotalRecord((int) totalRecord);
		// 获取当时页中的记载寄存的List
		String sql2 = "select id,title,author,price,sales,stock,img_path imgPath from books where price between ? and ? limit ?,?";
		// 调用BaseDao中获取一个调集的办法
		List<Book> beanList = getBeanList(conn,sql2, minPrice , maxPrice , (page.getPageNo() - 1) * Page.PAGE_SIZE, Page.PAGE_SIZE);
		// 将这个List设置到page目标中
		page.setList(beanList);
		return page;
	}
}

【UserDaoImpl.java】

package com.atguigu.bookstore.dao.impl;
import java.sql.Connection;
import com.atguigu.bookstore.beans.User;
import com.atguigu.bookstore.dao.BaseDao;
import com.atguigu.bookstore.dao.UserDao;
public class UserDaoImpl extends BaseDao<User> implements UserDao {
	@Override
	public User getUser(Connection conn,User user) {
		// 调用BaseDao中获取一个目标的办法
		User bean = null;
		// 写sql句子
		String sql = "select id,username,password,email from users where username = ? and password = ?";
		bean = getBean(conn,sql, user.getUsername(), user.getPassword());
		return bean;
	}
	@Override
	public boolean checkUsername(Connection conn,User user) {
		// 调用BaseDao中获取一个目标的办法
		User bean = null;
		// 写sql句子
		String sql = "select id,username,password,email from users where username = ?";
		bean = getBean(conn,sql, user.getUsername());
		return bean != null;
	}
	@Override
	public void saveUser(Connection conn,User user) {
		//写sql句子
		String sql = "insert into users(username,password,email) values(?,?,?)";
		//调用BaseDao中通用的增修改的办法
		update(conn,sql, user.getUsername(),user.getPassword(),user.getEmail());
	}
}

【Book.java】

package com.atguigu.bookstore.beans;
/**
 * 图书类
 * @author songhongkang
 *
 */
public class Book {
	private Integer id;
	private String title; // 书名
	private String author; // 作者
	private double price; // 价格
	private Integer sales; // 销量
	private Integer stock; // 库存
	private String imgPath = "static/img/default.jpg"; // 封面图片的途径
	//结构器,get(),set(),toString()办法略
}

【Page.java】

package com.atguigu.bookstore.beans;
import java.util.List;
/**
 * 页码类
 * @author songhongkang
 *
 */
public class Page<T> {
	private List<T> list; // 每页查到的记载寄存的调集
	public static final int PAGE_SIZE = 4; // 每页显现的记载数
	private int pageNo; // 当时页
//	private int totalPageNo; // 总页数,经过核算得到
	private int totalRecord; // 总记载数,经过查询数据库得到

【User.java】

package com.atguigu.bookstore.beans;
/**
 * 用户类
 * @author songhongkang
 *
 */
public class User {
	private Integer id;
	private String username;
	private String password;
	private String email;

第8章:数据库衔接池

8.1 JDBC数据库衔接池的必要性

  • 在运用开发基于数据库的web程序时,传统的方式根本是按以下过程:  

    • 在主程序(如servlet、beans)中树立数据库衔接
    • 进行sql操作
    • 断开数据库衔接
  • 这种方式开发,存在的问题:

    • 一般的JDBC数据库衔接运用 DriverManager 来获取,每次向数据库树立衔接的时分都要将 Connection 加载到内存中,再验证用户名和暗码(得花费0.05s~1s的时刻)。需求数据库衔接的时分,就向数据库要求一个,履行完结后再断开衔接。这样的办法将会耗费很多的资源和时刻。数据库的衔接资源并没有得到很好的重复运用。 若一起有几百人乃至几千人在线,频繁的进行数据库衔接操作将占用很多的体系资源,严重的乃至会造成服务器的溃散。
    • 关于每一次数据库衔接,运用完后都得断开。 不然,假如程序呈现反常而未能封闭,将会导致数据库体系中的内存走漏,最终将导致重启数据库。(回忆:何为Java的内存走漏?)
    • 这种开发不能控制被创立的衔接目标数,体系资源会被毫无顾及的分配出去,如衔接过多,也或许导致内存走漏,服务器溃散。

8.2 数据库衔接池技能

  • 为处理传统开发中的数据库衔接问题,能够选用数据库衔接池技能。
  • 数据库衔接池的根本思维:便是为数据库衔接树立一个“缓冲池”。预先在缓冲池中放入必定数量的衔接,当需求树立数据库衔接时,只需从“缓冲池”中取出一个,运用完毕之后再放回去。
  • 数据库衔接池负责分配、办理和开释数据库衔接,它答应运用程序重复运用一个现有的数据库衔接,而不是从头树立一个
  • 数据库衔接池在初始化时将创立必定数量的数据库衔接放到衔接池中,这些数据库衔接的数量是由最小数据库衔接数来设定的。不管这些数据库衔接是否被运用,衔接池都将一向确保至少具有这么多的衔接数量。衔接池的最大数据库衔接数量约束了这个衔接池能占有的最大衔接数,当运用程序向衔接池恳求的衔接数超越最大衔接数量时,这些恳求将被加入到等候行列中。

JDBC核心技术

  • 作业原理:

JDBC核心技术

  • 数据库衔接池技能的长处

    1. 资源重用

    由于数据库衔接得以重用,防止了频繁创立,开释衔接引起的很多功能开支。在削减体系耗费的基础上,另一方面也添加了体系运转环境的平稳性。

    2. 更快的体系反应速度

    数据库衔接池在初始化进程中,往往现已创立了若干数据库衔接置于衔接池中备用。此刻衔接的初始化作业均已完结。关于业务恳求处理而言,直接运用现有可用衔接,防止了数据库衔接初始化和开释进程的时刻开支,然后削减了体系的响应时刻

    3. 新的资源分配手段

    关于多运用同享同一数据库的体系而言,可在运用层经过数据库衔接池的装备,完结某一运用最大可用数据库衔接数的约束,防止某一运用独占一切的数据库资源

    4. 一致的衔接办理,防止数据库衔接走漏

    在较为完善的数据库衔接池完结中,可依据预先的占用超时设定,强制收回被占用衔接,然后防止了惯例数据库衔接操作中或许呈现的资源泄露

8.3 多种开源的数据库衔接池

  • JDBC 的数据库衔接池运用 javax.sql.DataSource 来表明,DataSource 仅仅一个接口,该接口一般由服务器(Weblogic, WebSphere, Tomcat)供给完结,也有一些开源安排供给完结:

    • DBCP 是Apache供给的数据库衔接池。tomcat 服务器自带dbcp数据库衔接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再供给支撑。
    • C3P0 是一个开源安排供给的一个数据库衔接池,速度相对较慢,稳定性还能够。 hibernate官方引荐运用
    • Proxool 是sourceforge下的一个开源项目数据库衔接池,有监控衔接池状况的功用,稳定性较c3p0差一点
    • BoneCP 是一个开源安排供给的数据库衔接池,速度快
    • Druid 是阿里供给的数据库衔接池,据说是集DBCP 、C3P0 、Proxool 长处于一身的数据库衔接池,可是速度不确定是否有BoneCP快
  • DataSource 一般被称为数据源,它包含衔接池和衔接池办理两个部分,习惯上也经常把 DataSource 称为衔接池

  • DataSource用来替代DriverManager来获取Connection,获取速度快,一起能够大幅度提高数据库拜访速度。

  • 特别留意:

    • 数据源和数据库衔接不同,数据源无需创立多个,它是发生数据库衔接的工厂,因此整个运用只需求一个数据源即可。
    • 当数据库拜访完毕后,程序仍是像曾经相同封闭数据库衔接:conn.close(); 但conn.close()并没有封闭数据库的物理衔接,它仅仅把数据库衔接开释,归还给了数据库衔接池。

8.3.1 C3P0数据库衔接池

  • 获取衔接办法一
//运用C3P0数据库衔接池的办法,获取数据库的衔接:不引荐
public static Connection getConnection1() throws Exception{
	ComboPooledDataSource cpds = new ComboPooledDataSource();
	cpds.setDriverClass("com.mysql.jdbc.Driver"); 
	cpds.setJdbcUrl("jdbc:mysql://localhost:3306/test");
	cpds.setUser("root");
	cpds.setPassword("abc123");
//	cpds.setMaxPoolSize(100);
	Connection conn = cpds.getConnection();
	return conn;
}
  • 获取衔接办法二
//运用C3P0数据库衔接池的装备文件办法,获取数据库的衔接:引荐
private static DataSource cpds = new ComboPooledDataSource("helloc3p0");
public static Connection getConnection2() throws SQLException{
	Connection conn = cpds.getConnection();
	return conn;
}

其间,src下的装备文件为:【c3p0-config.xml】

<?xml version="1.0" encoding="UTF-8"?>
<c3p0-config>
	<named-config name="helloc3p0">
		<!-- 获取衔接的4个根本信息 -->
		<property name="user">root</property>
		<property name="password">abc123</property>
		<property name="jdbcUrl">jdbc:mysql:///test</property>
		<property name="driverClass">com.mysql.jdbc.Driver</property>
		<!-- 涉及到数据库衔接池的办理的相关特点的设置 -->
		<!-- 若数据库中衔接数缺乏时, 一次向数据库服务器请求多少个衔接 -->
		<property name="acquireIncrement">5</property>
		<!-- 初始化数据库衔接池时衔接的数量 -->
		<property name="initialPoolSize">5</property>
		<!-- 数据库衔接池中的最小的数据库衔接数 -->
		<property name="minPoolSize">5</property>
		<!-- 数据库衔接池中的最大的数据库衔接数 -->
		<property name="maxPoolSize">10</property>
		<!-- C3P0 数据库衔接池能够保护的 Statement 的个数 -->
		<property name="maxStatements">20</property>
		<!-- 每个衔接一起能够运用的 Statement 目标的个数 -->
		<property name="maxStatementsPerConnection">5</property>
	</named-config>
</c3p0-config>

8.3.2 DBCP数据库衔接池

  • DBCP 是 Apache 软件基金安排下的开源衔接池完结,该衔接池依靠该安排下的另一个开源体系:Common-pool。如需运用该衔接池完结,应在体系中添加如下两个 jar 文件:

    • Commons-dbcp.jar:衔接池的完结
    • Commons-pool.jar:衔接池完结的依靠库
  • Tomcat 的衔接池正是选用该衔接池来完结的。 该数据库衔接池既能够与运用服务器整合运用,也可由运用程序独立运用。

  • 数据源和数据库衔接不同,数据源无需创立多个,它是发生数据库衔接的工厂,因此整个运用只需求一个数据源即可。

  • 当数据库拜访完毕后,程序仍是像曾经相同封闭数据库衔接:conn.close(); 但上面的代码并没有封闭数据库的物理衔接,它仅仅把数据库衔接开释,归还给了数据库衔接池。

  • 装备特点阐明

特点 默许值 阐明
initialSize 0 衔接池启动时创立的初始化衔接数量
maxActive 8 衔接池中可一起衔接的最大的衔接数
maxIdle 8 衔接池中最大的闲暇的衔接数,超越的闲暇衔接将被开释,假如设置为负数表明不约束
minIdle 0 衔接池中最小的闲暇的衔接数,低于这个数量会被创立新的衔接。该参数越接近maxIdle,功能越好,由于衔接的创立和销毁,都是需求耗费资源的;可是不能太大。
maxWait 无约束 最大等候时刻,当没有可用衔接时,衔接池等候衔接开释的最大时刻,超越该时刻约束会抛出反常,假如设置-1表明无限等候
poolPreparedStatements false 敞开池的Statement是否prepared
maxOpenPreparedStatements 无约束 敞开池的prepared 后的一起最大衔接数
minEvictableIdleTimeMillis 衔接池中衔接,在时刻段内一向闲暇, 被逐出衔接池的时刻
removeAbandonedTimeout 300 超越时刻约束,收回没有用(废弃)的衔接
removeAbandoned false 超越removeAbandonedTimeout时刻后,是否进 行没用衔接(废弃)的收回
  • 获取衔接办法一:
public static Connection getConnection3() throws Exception {
	BasicDataSource source = new BasicDataSource();
	source.setDriverClassName("com.mysql.jdbc.Driver");
	source.setUrl("jdbc:mysql:///test");
	source.setUsername("root");
	source.setPassword("abc123");
	//
	source.setInitialSize(10);
	Connection conn = source.getConnection();
	return conn;
}
  • 获取衔接办法二:
//运用dbcp数据库衔接池的装备文件办法,获取数据库的衔接:引荐
private static DataSource source = null;
static{
	try {
		Properties pros = new Properties();
		InputStream is = DBCPTest.class.getClassLoader().getResourceAsStream("dbcp.properties");
		pros.load(is);
		//依据供给的BasicDataSourceFactory创立对应的DataSource目标
		source = BasicDataSourceFactory.createDataSource(pros);
	} catch (Exception e) {
		e.printStackTrace();
	}
}
public static Connection getConnection4() throws Exception {
	Connection conn = source.getConnection();
	return conn;
}

其间,src下的装备文件为:【dbcp.properties】

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true&useServerPrepStmts=false
username=root
password=abc123
initialSize=10
#...

8.3.3 Druid(德鲁伊)数据库衔接池

Druid是阿里巴巴开源渠道上一个数据库衔接池完结,它结合了C3P0、DBCP、Proxool等DB池的长处,一起加入了日志监控,能够很好的监控DB池衔接和SQL的履行情况,能够说是针对监控而生的DB衔接池,能够说是目前最好的衔接池之一。

package com.atguigu.druid;
import java.sql.Connection;
import java.util.Properties;
import javax.sql.DataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
public class TestDruid {
	public static void main(String[] args) throws Exception {
		Properties pro = new Properties();		 pro.load(TestDruid.class.getClassLoader().getResourceAsStream("druid.properties"));
		DataSource ds = DruidDataSourceFactory.createDataSource(pro);
		Connection conn = ds.getConnection();
		System.out.println(conn);
	}
}

其间,src下的装备文件为:【druid.properties】

url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
username=root
password=123456
driverClassName=com.mysql.jdbc.Driver
initialSize=10
maxActive=20
maxWait=1000
filters=wall
  • 具体装备参数:
装备 缺省 阐明
name 装备这个特点的含义在于,假如存在多个数据源,监控的时分能够经过姓名来区分开来。 假如没有装备,将会生成一个姓名,格式是:”DataSource-” + System.identityHashCode(this)
url 衔接数据库的url,不同数据库不相同。例如:mysql : jdbc:mysql://10.20.153.104:3306/druid2 oracle : jdbc:oracle:thin:@10.20.149.85:1521:ocnauto
username 衔接数据库的用户名
password 衔接数据库的暗码。假如你不希望暗码直接写在装备文件中,能够运用ConfigFilter。具体看这里:github.com/alibaba/dru…
driverClassName 依据url主动识别 这一项可配可不配,假如不装备druid会依据url主动识别dbType,然后挑选相应的driverClassName(主张装备下)
initialSize 0 初始化时树立物理衔接的个数。初始化发生在显现调用init办法,或者榜首次getConnection时
maxActive 8 最大衔接池数量
maxIdle 8 现已不再运用,装备了也没效果
minIdle 最小衔接池数量
maxWait 获取衔接时最大等候时刻,单位毫秒。装备了maxWait之后,缺省启用公正锁,并发功率会有所下降,假如需求能够经过装备useUnfairLock特点为true运用非公正锁。
poolPreparedStatements false 是否缓存preparedStatement,也便是PSCache。PSCache对支撑游标的数据库功能提高巨大,比如说oracle。在mysql下主张封闭。
maxOpenPreparedStatements -1 要启用PSCache,有必要装备大于0,当大于0时,poolPreparedStatements主动触发修正为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,能够把这个数值装备大一些,比如说100
validationQuery 用来检测衔接是否有用的sql,要求是一个查询句子。假如validationQuery为null,testOnBorrow、testOnReturn、testWhileIdle都不会其作用。
testOnBorrow true 请求衔接时履行validationQuery检测衔接是否有用,做了这个装备会下降功能。
testOnReturn false 归还衔接时履行validationQuery检测衔接是否有用,做了这个装备会下降功能
testWhileIdle false 主张装备为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,是组合联系,并非替换联系

第9章:Apache-DBUtils完结CRUD操作

9.1 Apache-DBUtils简介

  • commons-dbutils 是 Apache 安排供给的一个开源 JDBC东西类库,它是对JDBC的简略封装,学习成本极低,而且运用dbutils能极大简化jdbc编码的作业量,一起也不会影响程序的功能。

  • API介绍:

    • org.apache.commons.dbutils.QueryRunner
    • org.apache.commons.dbutils.ResultSetHandler
    • 东西类:org.apache.commons.dbutils.DbUtils
  • API包阐明:

JDBC核心技术

JDBC核心技术

9.2 首要API的运用

9.2.1 DbUtils

  • DbUtils :供给如封闭衔接、装载JDBC驱动程序等惯例作业的东西类,里面的一切办法都是静态的。首要办法如下:

    • public static void close(…) throws java.sql.SQLException: DbUtils类供给了三个重载的封闭办法。这些办法检查所供给的参数是不是NULL,假如不是的话,它们就封闭Connection、Statement和ResultSet。
    • public static void closeQuietly(…): 这一类办法不仅能在Connection、Statement和ResultSet为NULL情况下防止封闭,还能隐藏一些在程序中抛出的SQLEeception。
    • public static void commitAndClose(Connection conn)throws SQLException: 用来提交衔接的业务,然后封闭衔接
    • public static void commitAndCloseQuietly(Connection conn): 用来提交衔接,然后封闭衔接,而且在封闭衔接时不抛出SQL反常。
    • public static void rollback(Connection conn)throws SQLException:答应conn为null,由于办法内部做了判断
    • public static void rollbackAndClose(Connection conn)throws SQLException
    • rollbackAndCloseQuietly(Connection)
    • public static boolean loadDriver(java.lang.String driverClassName):这一方装载并注册JDBC驱动程序,假如成功就回来true。运用该办法,你不需求捕捉这个反常ClassNotFoundException。

9.2.2 QueryRunner类

  • 该类简略化了SQL查询,它与ResultSetHandler组合在一起运用能够完结大部分的数据库操作,能够大大削减编码量。

  • QueryRunner类供给了两个结构器:

    • 默许的结构器
    • 需求一个 javax.sql.DataSource 来作参数的结构器
  • QueryRunner类的首要办法:

    • 更新

      • public int update(Connection conn, String sql, Object… params) throws SQLException:用来履行一个更新(刺进、更新或删除)操作。
      • ……
    • 刺进

      • public <T> T insert(Connection conn,String sql,ResultSetHandler<T> rsh, Object… params) throws SQLException:只支撑INSERT句子,其间 rsh – The handler used to create the result object from the ResultSet of auto-generated keys. 回来值: An object generated by the handler.即主动生成的键值
      • ….
    • 批处理

      • public int[] batch(Connection conn,String sql,Object[](#) params)throws SQLException: INSERT, UPDATE, or DELETE句子
      • public <T> T insertBatch(Connection conn,String sql,ResultSetHandler<T> rsh,Object[](#) params)throws SQLException:只支撑INSERT句子
      • …..
    • 查询

      • public Object query(Connection conn, String sql, ResultSetHandler rsh,Object… params) throws SQLException:履行一个查询操作,在这个查询中,目标数组中的每个元素值被用来作为查询句子的置换参数。该办法会自行处理 PreparedStatement 和 ResultSet 的创立和封闭。
      • ……
  • 测验

// 测验添加
@Test
public void testInsert() throws Exception {
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	String sql = "insert into customers(name,email,birth)values(?,?,?)";
	int count = runner.update(conn, sql, "何成飞", "he@qq.com", "1992-09-08");
	System.out.println("添加了" + count + "条记载");
	JDBCUtils.closeResource(conn, null);
}
// 测验删除
@Test
public void testDelete() throws Exception {
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	String sql = "delete from customers where id < ?";
	int count = runner.update(conn, sql,3);
	System.out.println("删除了" + count + "条记载");
	JDBCUtils.closeResource(conn, null);
}

9.2.3 ResultSetHandler接口及完结类

  • 该接口用于处理 java.sql.ResultSet,将数据按要求转化为另一种方式。

  • ResultSetHandler 接口供给了一个独自的办法:Object handle (java.sql.ResultSet .rs)。

  • 接口的首要完结类:

    • ArrayHandler:把成果会集的榜首行数据转成目标数组。
    • ArrayListHandler:把成果会集的每一行数据都转成一个数组,再寄存到List中。
    • BeanHandler: 将成果会集的榜首行数据封装到一个对应的JavaBean实例中。
    • BeanListHandler: 将成果会集的每一行数据都封装到一个对应的JavaBean实例中,寄存到List里。
    • ColumnListHandler:将成果会集某一列的数据寄存到List中。
    • KeyedHandler(name):将成果会集的每一行数据都封装到一个Map里,再把这些map再存到一个map里,其key为指定的key。
    • MapHandler: 将成果会集的榜首行数据封装到一个Map里,key是列名,value便是对应的值。
    • MapListHandler: 将成果会集的每一行数据都封装到一个Map里,然后再寄存到List
    • ScalarHandler: 查询单个值目标
  • 测验

/*
 * 测验查询:查询一条记载
 * 
 * 运用ResultSetHandler的完结类:BeanHandler
 */
@Test
public void testQueryInstance() throws Exception{
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	String sql = "select id,name,email,birth from customers where id = ?";
	//
	BeanHandler<Customer> handler = new BeanHandler<>(Customer.class);
	Customer customer = runner.query(conn, sql, handler, 23);
	System.out.println(customer);	
	JDBCUtils.closeResource(conn, null);
}
/*
 * 测验查询:查询多条记载构成的调集
 * 
 * 运用ResultSetHandler的完结类:BeanListHandler
 */
@Test
public void testQueryList() throws Exception{
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	String sql = "select id,name,email,birth from customers where id < ?";
	//
	BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class);
	List<Customer> list = runner.query(conn, sql, handler, 23);
	list.forEach(System.out::println);
	JDBCUtils.closeResource(conn, null);
}
/*
 * 自界说ResultSetHandler的完结类
 */
@Test
public void testQueryInstance1() throws Exception{
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	String sql = "select id,name,email,birth from customers where id = ?";
	ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() {
		@Override
		public Customer handle(ResultSet rs) throws SQLException {
			System.out.println("handle");
//			return new Customer(1,"Tom","tom@126.com",new Date(123323432L));
			if(rs.next()){
				int id = rs.getInt("id");
				String name = rs.getString("name");
				String email = rs.getString("email");
				Date birth = rs.getDate("birth");
				return new Customer(id, name, email, birth);
			}
			return null;
		}
	};
	Customer customer = runner.query(conn, sql, handler, 23);
	System.out.println(customer);
	JDBCUtils.closeResource(conn, null);
}
/*
 * 怎么查询类似于最大的,最小的,均匀的,总和,个数相关的数据,
 * 运用ScalarHandler
 * 
 */
@Test
public void testQueryValue() throws Exception{
	QueryRunner runner = new QueryRunner();
	Connection conn = JDBCUtils.getConnection3();
	//测验一:
//	String sql = "select count(*) from customers where id < ?";
//	ScalarHandler handler = new ScalarHandler();
//	long count = (long) runner.query(conn, sql, handler, 20);
//	System.out.println(count);
	//测验二:
	String sql = "select max(birth) from customers";
	ScalarHandler handler = new ScalarHandler();
	Date birth = (Date) runner.query(conn, sql, handler);
	System.out.println(birth);
	JDBCUtils.closeResource(conn, null);
}

JDBC总结

总结
@Test
public void testUpdateWithTx() {
	Connection conn = null;
	try {
		//1.获取衔接的操作(
		//① 手写的衔接:JDBCUtils.getConnection();
		//② 运用数据库衔接池:C3P0;DBCP;Druid
		//2.对数据表进行一系列CRUD操作
		//① 运用PreparedStatement完结通用的增修改、查询操作(version 1.0 \ version 2.0)
//version2.0的增修改public void update(Connection conn,String sql,Object ... args){}
//version2.0的查询 public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object ... args){}
		//② 运用dbutils供给的jar包中供给的QueryRunner类
		//提交数据
		conn.commit();
	} catch (Exception e) {
		e.printStackTrace();
		try {
			//回滚数据
			conn.rollback();
		} catch (SQLException e1) {
			e1.printStackTrace();
		}
	}finally{
		//3.封闭衔接等操作
		//① JDBCUtils.closeResource();
		//② 运用dbutils供给的jar包中供给的DbUtils类供给了封闭的相关操作
	}
}