JDBC与数据库总结
JDBC API小结
两种思想
面向接口编程的思想
ORM思想(object relational mapping)
一个数据表对应一个java类
表中的一条记录对应java类的一个对象
表中的一个字段对应java类的一个属性
sql是需要结合列名和表的属性名来写。注意起别名。
两种技术
JDBC结果集的元数据:ResultSetMetaData
获取列数:getColumnCount()
获取列的别名:getColumnLabel()
通过反射,创建指定类的对象,获取指定的属性并赋值
以下的代码演示是基于MySQL-8.0.27
手写JDBC的CRUD操作
案例:创立数据库表 examstudent,表结构如下:
向数据表中添加如下数据:
代码实现1:插入一个新的student 信息
请输入考生的详细信息
Type:
IDCard:
ExamCard:
StudentName:
Location:
Grade:
信息录入成功!
代码实现2:在 eclipse中建立 java 程序:输入身份证号或准考证号可以查询到学生的基本信息。结果如下:
代码实现3:完成学生信息的删除功能
代码演示(这里没有把JDBC的连接/关闭,单独封装为utils):
package com.jerry.exer; import org.junit.Test; import java.io.InputStream; import java.lang.reflect.Field; import java.sql.*; import java.util.Properties; import java.util.Scanner; /** * @author jerry_jy * @create 2022-10-19 9:13 */ public class Exer2 { //TODO 1.向表中插入数据 @Test public void testInsert() { Scanner s = new Scanner(System.in); System.out.println("type:"); int type = s.nextInt(); System.out.println("IDCard:"); String idCard = s.next(); System.out.println("examCard:"); String examCard = s.next(); System.out.println("StudentName:"); String studentName = s.next(); System.out.println("Location:"); String location = s.next(); System.out.println("Grade:"); int grade = s.nextInt(); String sql = "insert into examstudent(type,idcard,examcard,studentname,location,grade)" + "values(?,?,?,?,?,?)"; update(sql,type,idCard,examCard,studentName,location,grade); System.out.println("录入成功!"); } //通用的增删改操作 public static void update(String sql, Object... args) { Connection conn = null; PreparedStatement ps = null; try { // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); //4.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); //5.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } //6.执行操作 ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { //7.资源的关闭 try { if (ps != null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } } //TODO 3.删除(法二):较好! @Test public void testDelete1() { Scanner s = new Scanner(System.in); System.out.println("请输入学生的考号:"); String examCard = s.next(); String sql = "delete from examstudent where examcard = ?"; boolean b = delete(sql,examCard); if (b) { System.out.println("删除成功!"); } else { System.out.println("查无此人"); } } // 3.删除(法一) @Test public void testDelete() throws Exception { Scanner s = new Scanner(System.in); System.out.println("请输入学生的考号:"); String examCard = s.next(); String sql = "delete from examstudent where examcard = ?"; String sql1 = "select Type type,IDCard IDCard,ExamCard examCard,Location location,StudentName studentName,Grade grade from examstudent" + " where examCard = ?"; Student student = getInstance(Student.class,sql1,examCard); if (student != null) { update(sql,examCard); System.out.println("删除成功"); } else { System.out.println("查无此人"); } } // 删除操作,返回是否有数据被删除 public static boolean delete(String sql, Object... args) { // 1.获取数据库的连接 Connection conn = null; // 2.获取一个PreparedStatement的对象 PreparedStatement ps = null; try { // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); //4.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); //5.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 6.通过PreparedStatement的对象执行sql语句 int i = ps.executeUpdate(); if (i > 0) { return true; } else { return false; } } catch (Exception e) { // Auto-generated catch block e.printStackTrace(); } finally { //7.资源的关闭 try { if (ps != null) ps.close(); } catch (SQLException e) { e.printStackTrace(); } try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } return false; } //TODO 2.查询(身份证号、准考证号) @Test public void testQuery() throws Exception { System.out.println("请选择您要输入的类型:\na:准考证号\nb:身份证号:"); Scanner s = new Scanner(System.in); String type = s.next(); if (type.equalsIgnoreCase("a")) { System.out.println("请输入准考证号:"); String examCard = s.next(); String sql = "select Type type,IDCard IDCard,ExamCard examCard,Location location,StudentName studentName,Grade grade from examstudent" + " where examCard = ?"; Student s1 = getInstance(Student.class,sql,examCard); if (s1 != null) { System.out.println(s1); } else { System.out.println("查无此人"); } } else if (type.equalsIgnoreCase("b")) { System.out.println("请输入身份证号:"); String IDCard = s.next(); String sql = "select Type type,IDCard IDCard,ExamCard examCard,Location location,StudentName studentName,Grade grade from examstudent where IDCard = ?"; Student s1 = getInstance(Student.class,sql,IDCard); if (s1 != null) { System.out.println(s1); } else { System.out.println("查无此人"); } } else { System.out.println("您输入的信息有误!请重新进入"); } } //通用的查询 public <T> T getInstance(Class<T> clazz, String sql, Object... args) throws Exception { // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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); //4.预编译sql语句,返回PreparedStatement的实例 PreparedStatement ps = conn.prepareStatement(sql); //5.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } //6.执行操作 ResultSet rs = ps.executeQuery(); // 获取结果集的元数据 :ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); // 通过ResultSetMetaData获取结果集中的列数 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 columnName = rsmd.getColumnName(i + 1); String columnLabel = rsmd.getColumnLabel(i + 1); // 给t对象指定的columnName属性,赋值为columValue:通过反射 Field field = clazz.getDeclaredField(columnLabel); field.setAccessible(true); field.set(t, columnVal); } // 返回查询的结果集对象 return t; } // 手动关闭资源操作 rs.close(); ps.close(); conn.close(); return null; } }
数据库连接池
JDBC数据库连接池的必要性
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
在主程序(如servlet、beans)中建立数据库连接
进行sql操作
断开数据库连接
这种模式开发,存在的问题:
普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。数据库的连接资源并没有得到很好的重复利用若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
对于每一次数据库连接,使用完后都得断开否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
数据库连接池
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()并没有关闭数据库的物理连接,它仅仅把数据库连接释放,归还给了数据库连接池。
代码演示:Druid连接池
package com.jerry.java; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; import javax.sql.DataSource; import org.junit.Test; import com.alibaba.druid.pool.DruidDataSourceFactory; /** * @author jerry_jy * @create 2022-10-19 11:22 */ public class DruidTest { @Test public void getConnection() throws Exception{ Properties pros = new Properties(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"); pros.load(is); DataSource source = DruidDataSourceFactory.createDataSource(pros); Connection conn = source.getConnection(); System.out.println(conn); } }
druid.properties(位置是在src下面)
url=jdbc:mysql://localhost:3306/test username=root password=root driverClassName=com.mysql.cj.jdbc.Driver initialSize=10 maxActive=10
Apache-DBUtils实现CRUD操作
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
QueryRunner类的主要方法:
更新
public int update(Connection conn, String sql, Object… params) throws SQLException:用来执行一个更新(插入、更新或删除)操作。
…
update插入操作
//测试插入 // QueryRunner().upadte @Test public void testInsert() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); String sql = "insert into customers(name,email,birth)values(?,?,?)"; int insertCount = runner.update(conn, sql, "蔡徐坤", "caixukun@126.com", "1997-09-08"); System.out.println("添加了" + insertCount + "条记录"); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
BeanHander查询一条记录
//测试查询 /* * BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录。 * Customer [id=31, name=蔡徐坤, email=caixukun@126.com, birth=1997-09-08] */ @Test public void testQuery1() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); 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, 31); System.out.println(customer); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
BeanListHandler封装表中的多条记录构成的集合
/* * BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合。 */ @Test public void testQuery2() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); String sql = "select id,name,email,birth from customers where id <= ?"; BeanListHandler<Customer> handler = new BeanListHandler<>(Customer.class); runner.query(conn, sql, handler, 31).stream().forEach(System.out::println); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
MapHander查询表中的一条记录
/* * MapHander:是ResultSetHandler接口的实现类,对应表中的一条记录。 * 将字段及相应字段的值作为map中的key和value * {name=jerry2, birth=1998-05-17, id=23, email=jerry@126.com} */ @Test public void testQuery3() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); String sql = "select id,name,email,birth from customers where id = ?"; MapHandler handler = new MapHandler(); Map<String, Object> map = runner.query(conn, sql, handler, 23); System.out.println(map); } catch (IOException | SQLException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
MapListHander查询表中的多条记录
/* * MapListHander:是ResultSetHandler接口的实现类,对应表中的多条记录。 * 将字段及相应字段的值作为map中的key和value。将这些map添加到List中 */ @Test public void testQuery4() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); String sql = "select id,name,email,birth from customers where id < ?"; MapListHandler handler = new MapListHandler(); runner.query(conn, sql, handler, 31).stream().forEach(System.out::println); } catch (SQLException | ClassNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
ScalarHandler:用于查询特殊值
/* * ScalarHandler:用于查询特殊值 */ @Test public void testQuery5() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); String sql = "select count(*) from customers"; ScalarHandler handler = new ScalarHandler(); Long count = (Long) runner.query(conn, sql, handler); System.out.println(count); } catch (SQLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
自定义ResultSetHandler的实现类
/* * 自定义ResultSetHandler的实现类 */ @Test public void testQuery7() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); // 1.读取配置文件中的4个基本信息 InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("jdbc.properties"); Properties pros = new Properties(); pros.load(is); 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.获取连接 conn = DriverManager.getConnection(url, user, password); String sql = "select id,name,email,birth from customers where id = ?"; ResultSetHandler<Customer> handler = new ResultSetHandler<Customer>() { @Override public Customer handle(ResultSet resultSet) throws SQLException { // return null; // return new Customer(32, "成龙", "Jacky@126.com", new Date(234324234324L)); if(resultSet.next()){ int id = resultSet.getInt("id"); String name = resultSet.getString("name"); String email = resultSet.getString("email"); Date birth = resultSet.getDate("birth"); Customer customer = new Customer(id, name, email, birth); return customer; } return null; } }; Customer customer = runner.query(conn, sql, handler, 13); System.out.println(customer); } catch (SQLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } finally { try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); } } }
数据库
事务的ACID属性
1.原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
2.一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。
3.隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。
4.持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
数据库的并发问题
对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题。
一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务隔离级别, 不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱。
四种隔离级别
数据库提供的4种事务隔离级别:
Oracle 支持的 2 种事务隔离级别:READ COMMITED, SERIALIZABLE。 Oracle 默认的事务隔离级别为: READ COMMITED 。
Mysql 支持 4 种事务隔离级别。Mysql 默认的事务隔离级别为: REPEATABLE READ。
在MySql中设置隔离级别
每启动一个 mysql 程序, 就会获得一个单独的数据库连接. 每个数据库连接都有一个全局变量 @@tx_isolation, 表示当前的事务隔离级别。
查看当前的隔离级别:
SELECT @@tx_isolation;
设置当前 mySQL 连接的隔离级别:
set transaction isolation level read committed;
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
–end–