JDBC学习笔记
JDBC概述
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API),定义了用来访问数据库的标准Java类库。不同的数据库厂商,需要针对这套接口,提供不同实现。不同实现的集合,即为不同数据库的驱动;java程序员则只需要面向这套接口编程即可。
JDBC API体现了面向接口编程和ORM(object relational mapping)思想,在使用上结合了元数据和反射的技术。
ODBC(Open Database Connectivity,开放式数据库连接)是微软在Windows平台下推出的。使用者在程序中只需要调用ODBC API,由ODBC驱动程序将调用转换成为对特定的数据库的调用请求。设计思想同JDBC
数据库的连接
获取一个数据库连接需要导入JDBC驱动(Driver接口实现类)、提供JDBC URL、连接数据库的用户名和密码。
JDBC驱动_:_java.sql.Driver接口是所有JDBC驱动程序需要实现的接口,使用驱动程序前需注册驱动。
- 使用获取运行时类的方式加载驱动(即初始化Driver接口实现类)
- DriverManager类是驱动程序管理器类,负责管理驱动程序,可以显式调用该类的registerDriver()方法来注册驱动程序类的实例
JDBC URL:标识注册的驱动程序,驱动程序管理器通过URL选择正确的驱动程序,从而建立到数据库的连接。JDBC URL的标识由三部分组成,各部分间用冒号分隔,
jdbc:子协议:子名称
- 协议:JDBC URL中的协议总是jdbc
- 子协议:子协议用于标识一个数据库驱动程序
- 子名称:数据库连接的信息,包括主机名、端口号、数据库名
//显式调用第三方jar包,并使用硬编码方式传参@Testpublic void testmethod0() throws SQLException {Driver driver = new com.mysql.cj.jdbc.Driver();//Driver driver = new com.mysql.jdbc.Driver();适用MySQL5.7,操作会报方法deprecated//mysql8.0的连接使用com.mysql.cj.jdbc.Driver,需加时区String url = "jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai";Properties info = new Properties();info.setProperty("user","root");info.setProperty("password","mysql");//创建连接Connection connect = driver.connect(url, info);System.out.println(connect);//关闭连接connect.close();}//显式调用第三方jar包,使用配置文件读取参数@Testpublic void testmethod1() throws Exception {Driver driver = new com.mysql.cj.jdbc.Driver();Properties info = new Properties();FileInputStream fis = new FileInputStream("jdbc.properties");//url中指明开启批处理String url = "jdbc:mysql://localhost:3306/testdb?serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true";info.load(fis);//创建连接Connection connect = driver.connect(url, info);System.out.println(connect);//关闭连接fis.close();connect.close();}//使用DriverManager管理驱动,显式注册驱动@Testpublic void testmethod2() throws Exception {Driver driver = new com.mysql.cj.jdbc.Driver();FileInputStream fis = new FileInputStream("jdbc.properties");Properties info = new Properties();info.load(fis);String url = info.getProperty("url");//显式注册驱动DriverManager.registerDriver(driver);//创建连接Connection connection1 = DriverManager.getConnection(url, info);System.out.println(connection1);//创建连接String user = info.getProperty("user");String password = info.getProperty("password");Connection connection2 = DriverManager.getConnection(url, user, password);System.out.println(connection2);//关闭连接(略)}//使用DriverManager管理驱动,利用Driver自动为DriverManager注册驱动@Testpublic void testmethod3() throws Exception {FileInputStream fis = new FileInputStream("jdbc.properties");Properties info = new Properties();info.load(fis);String url = info.getProperty("url");//触发类的加载,Driver自动为DriverManager注册驱动Class.forName("com.mysql.cj.jdbc.Driver");//创建连接Connection connection = DriverManager.getConnection(url, info);System.out.println(connection);//关闭连接connection.close();}//使用DriverManager管理驱动,利用Driver自动为DriverManager注册驱动//将驱动路径也写入配置文件@Testpublic void testmethod4() throws Exception {//加载配置文件FileInputStream fis = new FileInputStream("jdbc.properties");Properties info = new Properties();info.load(fis);String url = info.getProperty("url");String classname = info.getProperty("classname");//触发类的加载,Driver自动为DriverManager注册驱动Class.forName(classname);//创建连接Connection connection = DriverManager.getConnection(url, info);System.out.println(connection);//关闭连接connection.close();}
JDBC的CRUD操作
【Statement和PreparedStatement】 Statement操作数据表存在的弊端:一是拼串操作繁琐;二是存在SQL注入问题 PreparedStatement接口是Statement的子接口,它表示一条预编译过的SQL语句,语句中的参数用?填充。预编译的特性能提高执行性能,且防止SQL注入
Java与SQL的数据类型存在对应关系,在SQL查询得到的结果集中按对应关系转换为Java的数据类型。
ResultSet:查询需要调用PreparedStatement的executeQuery()方法,查询结果是一个ResultSet对象。对象以逻辑表格的形式封装了执行查询操作的结果集,ResultSet接口由数据库厂商提供实现。ResultSet实际上就是一张数据表,ResultSet对象维护了一个指向当前数据行的游标,游标初始位置在第一行之前,可调用next()方法检测是否存在下一行。若有效,该方法返回true且指针下移(相当于Iterator对象的hasNext()和next()方法的结合体) 注意:Java与数据库交互涉及到的相关Java API中的索引都从1开始
ResultSetMetaData:查询结果集中的元数据,可以获取查询结果集的列数、列名、列别名、类型等信息。主要方法如下,详见API文档 getColumnName(int column):获取指定列的名称 getColumnLabel(int column):获取指定列的别名 getColumnCount():返回当前 ResultSet 对象中的列数 getColumnTypeName(int column):检索指定列的数据库特定的类型名称

资源释放:数据库连接使用完后需释放资源,包括ResultSet(结果集)、Statement(操作指令)、Connection(数据库连接)
DAO(Data Access Object):一组访问数据信息的类和接口,包括了对数据的CRUD操作(Create、Retrieve、Update、 Delete),而不包含任何业务相关的信息。 作用:实现功能的模块化,更有利于代码的维护和升级
public class JDBCutils {//静态变量private static Properties info = new Properties();private static String url;//仅在有连接指明使用数据库连接池技术才给dataSource赋值private static DataSource dataSource;//利用静态代码块初始化类static {FileInputStream fis = null;try {//读取配置文件fis = new FileInputStream("jdbc.properties");//在类中声明的info变量必须赋值,否则无法调用该方法info.load(fis);url = info.getProperty("url");String classname = info.getProperty("classname");//加载驱动Class.forName(classname);} catch (IOException e) {e.printStackTrace();} catch (ClassNotFoundException e) {e.printStackTrace();} finally {try {if (fis != null) {fis.close();}} catch (IOException e) {e.printStackTrace();}}}//返回数据库连接public static Connection getConnection() throws SQLException {return DriverManager.getConnection(url, info);}//返回具有事务特性的数据库连接public static Connection getTraConnection() throws SQLException {Connection conn = DriverManager.getConnection(url, info);conn.setAutoCommit(false);return conn;}//使用druid连接池,返回连接public static Connection getDruidConnection(String propertiesfile) throws Exception {// DataSource dataSource = DruidDataSourceFactory.createDataSource(pro);if (dataSource == null) {synchronized (JDBCutils.class) {if (dataSource == null) {Properties properties = new Properties();properties.load(new FileInputStream(propertiesfile));dataSource = DruidDataSourceFactory.createDataSource(properties);}}}Connection connection = dataSource.getConnection();return connection;}//销毁连接池public static void DataSourceDestroy() throws Exception {if (dataSource != null) {Class clazz = dataSource.getClass();Method close = clazz.getDeclaredMethod("close");close.invoke(dataSource);}}//关闭资源--方法:Connection conn, Statement pspublic static void close(Connection conn, Statement ps) {if (conn != null) {try {conn.setAutoCommit(true);conn.close();} catch (SQLException e) {e.printStackTrace();}}if (ps != null) {try {ps.close();} catch (SQLException e) {e.printStackTrace();}}}//关闭资源--重载方法:Connection conn, Statement ps, ResultSet rspublic static void close(Connection conn, Statement ps, ResultSet rs) {close(conn, ps);if (rs != null) {try {rs.close();} catch (SQLException e) {e.printStackTrace();}}}//查询时对获取的ResultSet进行解析,利用metadata和反射//解析rs中的数据返回首条记录对应的对象//泛型方法的泛型由传入的参数决定,所以必须在传入的参数中给出类型public static <T> T parseone(Class<T> clazz, ResultSet rs) throws Exception {ResultSetMetaData rsmd = rs.getMetaData();//元数据int columnCount = rsmd.getColumnCount();//获取列数T t = clazz.newInstance();//获取运行时类对象的实例if (rs.next()) {for (int i = 1; i <= columnCount; i++) {//获取对应的属性名和属性值String columnLabel = rsmd.getColumnLabel(i);Object object = rs.getObject(columnLabel);//利用反射将属性赋值Field field = clazz.getDeclaredField(columnLabel);field.setAccessible(true);field.set(t, object);}}return t;}//解析rs中的数据返回多条记录对应的对象//泛型方法的泛型由传入的参数决定,所以必须在传入的参数中给出类型public static <T> ArrayList<T> parsemany(Class<T> clazz, ResultSet rs) throws Exception {ResultSetMetaData rsmd = rs.getMetaData();int columnCount = rsmd.getColumnCount();//获取列数ArrayList<T> ts = new ArrayList<>();T t = clazz.newInstance();//获取运行时类对象的实例while (rs.next()) {for (int i = 1; i <= columnCount; i++) {//获取对应的属性名和属性值String columnLabel = rsmd.getColumnLabel(i);Object object = rs.getObject(columnLabel);//利用反射将属性赋值Field field = clazz.getDeclaredField(columnLabel);field.setAccessible(true);field.set(t, object);}ts.add(t);}return ts;}}
JDBC的其它操作
批量操作:将SQL语句批量提交给数据库处理的效率通常比单独提交处理的效率高
- addBatch(String):添加需要批量处理的SQL语句
- executeBatch():执行批量处理语句
- clearBatch():清空缓存的数据
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.每达到500条语句提交批量执行ps.executeBatch();//3.清空ps.clearBatch();}}
事务:JDBC支持对事务进行操作
- 调用Connection对象的setAutoCommit(false),取消自动提交事务
- 在所有的SQL语句都成功执行后,调用commit()提交事务
- 方法提交事务在出现异常时,调用rollback()回滚事务
Connection conn = null;try {// 1.获取数据库连接conn = JDBCUtils.getConnection();// 2.开启事务conn.setAutoCommit(false);// 3.进行数据库操作String sql1 = "UPDATE person set account=account-500 WHERE NAME=?";PreparedStatement ps1 = conn.prepareStatement(sql1);ps1.setString(1,"xiaoming");ps1.executeUpdate();// 模拟网络异常System.out.println(10 / 0);String sql2 = "UPDATE person set account=account+500 WHERE NAME=?";PreparedStatement ps2 = conn.prepareStatement(sql2);ps2.setString(1,"xiaohong");ps2.executeUpdate();// 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.close(conn,ps1);JDBCutils.close(null,ps2);}
数据库连接池
为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕后再放回去。 数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池技术的优点:实现资源重用,避免数据库连接初始化和释放过程的时间开销,从而减少响应时间
//使用druid连接池,返回连接public static Connection getDruidConnection(String propertiesfile) throws Exception {if (dataSource == null) {synchronized (JDBCutils.class) {if (dataSource == null) {Properties properties = new Properties();properties.load(new FileInputStream(propertiesfile));dataSource = DruidDataSourceFactory.createDataSource(properties);}}}Connection connection = dataSource.getConnection();return connection;}
Apache-DBUtils实现CRUD操作
commons-dbutils是Apache组织提供的一个开源JDBC工具类库,它是对JDBC的简单封装。
- DbUtils:提供如关闭连接、装载JDBC驱动程序等常规工作的工具类
- QueryRunner:简化SQL查询,与ResultSetHandler组合在一起使用完成大部分的数据库操作
- ResultSetHandler:用于处理 java.sql.ResultSet,将数据按要求转换为另一种形式
@Testpublic void test1() throws Exception {//获取连接Connection druidConnection = JDBCutils.getDruidConnection("druid.properties");//构造SQL模板String sql1 = "UPDATE person SET account=account-200 WHERE account > 3000";String sql2 = "INSERT person(id,name,account) VALUES(?,?,?)";String sql3 = "DELETE FROM person WHERE id=?";//执行变更QueryRunner queryRunner = new QueryRunner();// int i = queryRunner.update(druidConnection, sql1);// System.out.println("变更了" + i + "条记录");int i = queryRunner.update(druidConnection, sql2,6,"xiaotang",3300);System.out.println("变更了" + i + "条记录");// int i = queryRunner.update(druidConnection, sql3,6);// System.out.println("变更了" + i + "条记录");//关闭资源DbUtils.close(druidConnection);}@Testpublic void test2() throws Exception {//获取连接Connection druidConnection = JDBCutils.getDruidConnection("druid.properties");//构造SQL模板String sql = "SELECT * FROM person WHERE account > ? AND account < ?";//使用resultsethandler解析查询的数据BeanHandler<Person> personBeanHandler = new BeanHandler<Person>(Person.class);BeanListHandler<Person> personBeanListHandler = new BeanListHandler<>(Person.class);//执行查询QueryRunner queryRunner = new QueryRunner();//接收首个对象Person oneperson = queryRunner.query(druidConnection, sql, personBeanHandler, 2000, 3800);System.out.println(oneperson);//接收对象的列表List<Person> manypeople = queryRunner.query(druidConnection, sql, personBeanListHandler, 2000, 3800);manypeople.forEach(System.out::println);//关闭资源DbUtils.close(druidConnection);
