JDBC概述
-
数据的持久化
持久化(persistence):把数据保存到可掉电式存储设备中以供之后使用。
-
JDBC的理解
JDBC(Java Database Connectivity)是一个独立于特定数据库管理系统、通用的SQL数据库存取和操作的公共接口(一组API)
简单理解为:JDBC,是SUN提供的一套 API,使用这套API可以实现对具体数据库的操作(获取连接、关闭连接、DML、DDL、DCL)
-
JDBC的好处
- 面向应用的API:Java API,抽象接口,供应用程序开发人员使用(连接数据库,执行SQL语句,获得结果)
- 面向数据库的API:Java Driver API,供开发商开发数据库驱动程序使用
- 从开发程序员的角度:不需要关注具体的数据库的细节
- 从数据库厂商的角度:只需要提供标准的具体实现
-
数据库的驱动
- 数据库厂商针对于JDBC这套接口,提供的具体实现类的集合
-
面向接口编程的思想
- JDBC是SUN公司提供的一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。
- 不同的数据库厂商,需要针对这套接口,提供不同的实现。不同的实现集合,即为不同数据库的驱动。
获取Connection对象的五种方式
-
方式一:
public void testConnection1() throws SQLException{ //获取Driver的实现类对象 Driver driver = new com.mysql.jdbc.Driver(); /* * 协议:jdbc * 子协议:mysql * 子名称:localhost:3306/test */ String url = "jdbc:mysql://localhost:3306/test"; //将用户名和密码封装在Properties中 Properties info = new Properties(); info.setProperty("user", "root"); info.setProperty("password", "root"); //获取连接对象 Connection conn = driver.connect(url, info); System.out.println(conn); }
-
方式二:对方式一的迭代:在如下程序中不出现第三方的api,使得程序具有更好的可移植性
public void testConnection2() throws Exception{ //1.获取Driver的实现类对象:使用反射 Class<?> clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); //2.提供要连接的数据库 String url = "jdbc:mysql://localhost:3306/test"; //3.提供连接需要的用户名、密码 Properties info = new Properties(); info.setProperty("user", "root"); info.setProperty("password", "root"); //4.获取连接 Connection conn = driver.connect(url, info); System.out.println(conn); }
-
方式三:使用DriverManager替换Driver
public void testConnection3() throws Exception{ //1.获取Driver的实现类对象 Class<?> clazz = Class.forName("com.mysql.jdbc.Driver"); Driver driver = (Driver) clazz.newInstance(); //2.提供另外三个连接的基本信息 String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "root"; //注册驱动 DriverManager.registerDriver(driver); //获取连接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); }
-
方式四:可以只是加载驱动,不用显式的注册驱动
public void testConnection4() throws Exception{ //1.提供三个连接的基本信息 String url = "jdbc:mysql://localhost:3306/test"; String user = "root"; String password = "root"; //2.加载驱动Driver Class.forName("com.mysql.jdbc.Driver"); //相较于方式三可以省略如下的操作: //Driver driver = (Driver) clazz.newInstance(); //注册驱动 //DriverManager.registerDriver(driver); //为什么可以省略上述操作呢? /* * 在mysql的Driver实现类中,声明了如下的操作 * static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } } */ //3.获取连接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); }
-
方式五(final,建议使用):将数据库连接需要的四个基本信息声明在配置文件中,通过读取配置文件的方式,获取连接
/* * 此种方式的好处: * 1.实现了数据与代码的分离,实现了解耦 * 2.如果需要修改配置文件信息,可以避免程序重新打包 */ public void testConnection5() throws Exception{ //1.读取配置文件中的四个基本信息 InputStream is = ConnectionTest.class.getClassLoader().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 driver = pros.getProperty("driver"); //2.加载驱动 Class.forName(driver); //3.获取连接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); }
jdbc.properties user=root
password=root
url=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
driver=com.mysql.jdbc.Driver
JDBCUtils工具类
public class JDBCUtils { /** * @Description 获取数据库的连接 * @return * @throws Exception */ public static Connection getConnection() throws Exception{ //读取配置文件(就是上文中的jdbc.properties)中的四个基本信息 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 driver = pros.getProperty("driver"); //加载驱动 Class.forName(driver); //获取连接 Connection conn = DriverManager.getConnection(url, user, password); return conn; } /** * @Description 关闭连接和Statement的操作 * @param stmt * @param conn */ public static void closeResource(Statement stmt,Connection conn){ try{ if(stmt != null) stmt.close(); if(conn != null) conn.close(); } catch(SQLException e){ e.printStackTrace(); } } /** * @Description 关闭连接、Statement和ResultSet的操作 * @param ps * @param conn * @param resultSet 结果集 */ public static void closeResource(PreparedStatement ps,Connection conn,ResultSet resultSet){ try{ if(ps != null) ps.close(); if(conn != null) conn.close(); if(resultSet != null) resultSet.close(); } catch(SQLException e){ e.printStackTrace(); } } }
Statement接口实现CRUD操作
/* * 实现对数据表中数据的增删改操作 */ public void update(String sql){ Connection conn = null; Statement st = null; try { //1.获取数据库的连接 conn = JDBCUtils.getConnection(); //2.创建一个Statement的实例 st = conn.createStatement(); //3.根据提供的sql语句,执行 st.execute(sql); } catch (Exception e) { e.printStackTrace(); }finally{ //4.资源的关闭 JDBCUtils.close(conn, st); } } /* * 实现对数据表的查询操作。需要使用结果集:ResultSet * */ public <T> T get(String sql, Class<T> clazz) {// (sql, Customer.class) T t = null; Connection conn = null; Statement st = null; ResultSet rs = null; try { conn = JDBCUtils.getConnection(); 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. 将数据表中得到的数据,封装进对象 // 注意:反射根据Java中类的属性获取Field对象 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; }
使用Statement的弊端
-
需要拼接sql语句,繁琐
String sql = "select user,password from user_table where user = '"+ user +"' and password = '"+password+"'";
-
存在SQL注入的问题
- SQL注入是利用某些系统没有对用户输入进行充分的检查,而在用户输入数据中注入非法的SQL语句段或者命令(如:select user,password from user_table where user=‘a’ or 1=’ and password=’ or ‘1’ = ‘1’),从而利用系统的SQL引擎完成恶意行为的做法。
-
其他问题
- Statement没办法操作Blob类型变量
- Statement实现批量插入时,效率较低
PreparedStatement的理解:
- PreparedStatement 是Statement的子接口
- An object that represents a precompiled SQL statement,可以预编译SQL语句
- 可以解决Statement的sql注入问题,拼串问题
PreparedStatement替换Statement实现CRUD操作
- 两种思想
- ORM编程思想(object relational mapping)
- 一个数据表对应一个java类
- 表中的一条记录对应java类的一个对象
- 表中的一个字段对应java类的一个属性
- 面向接口编程的思想
- ORM编程思想(object relational mapping)
- 两种技术
- 使用结果集的元数据:ResultSetMetaData
- getColumnCount():获取列数
- getColumnLabel():获取列的别名
- 说明:如果sql中没给字段其别名,getColumnLabel()获取的就是列名
- 反射的使用
- 创建对应的运行时类的对象
- 在运行时,动态的调用指定的运行时类的属性、方法)
- 使用结果集的元数据:ResultSetMetaData
- 通用的增删改操作(针对于不同表)
//通用的增删改操作 public void update(String sql,Object... args) { //sql中占位符的个数,应该与可变参的长度相同 Connection conn = null; PreparedStatement ps = 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.执行 ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { //5.资源的关闭 JDBCUtils.closeResource(ps, conn); } }
- 通用的查询操作(针对于不同表)
-
针对于表的字段名跟类的属性名不一致时:
-
1.必须在声明sql时,用类的属性名来命名字段的别名
-
2.在使用ResultSetMetaData时,需要使用getColumnLabel()来替换 getColumnName(),获取列的别名
-
说明:如果sql中没有给字段起别名,getColumnLabel()获取的就是列名
-
-
获取一个对象
/* * 使用PreparedStatement获取一个对象 */ public <T> T getInstance(Class<T> clazz,String sql,Object... args) { Connection conn = null; PreparedStatement ps = null; ResultSet resultSet = 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.执行,获取结果集 resultSet = ps.executeQuery(); //5.获取结果集的元数据ResultSetMetaData ResultSetMetaData metaData = resultSet.getMetaData(); //通过元数据获取结果集的列数 int columnCount = metaData.getColumnCount(); if(resultSet.next()){ T t = clazz.newInstance(); //处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for(int i = 0;i < columnCount;i++){ //获取列值 Object columnValue = resultSet.getObject(i + 1); //获取每个列的列名或者列的别名 String columnLabel = metaData.getColumnLabel(i + 1); //根据列名找到类中对应的属性 Field field = clazz.getDeclaredField(columnLabel); //将此对象的 accessible标志设置为true。 field.setAccessible(true); //将列值赋值给对应的对象的对应属性 field.set(t, columnValue); } return t; } } catch (Exception e) { e.printStackTrace(); } finally{ JDBCUtils.closeResource(ps, conn, resultSet); } return null; }
-
获取多个对象
public <T> List<T> getForList(Class<T> clazz,String sql,Object... args){ Connection conn = null; PreparedStatement ps = null; ResultSet resultSet = 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.执行,获取结果集 resultSet = ps.executeQuery(); //5.获取结果集的元数据ResultSetMetaData ResultSetMetaData metaData = resultSet.getMetaData(); //通过元数据获取结果集的列数 int columnCount = metaData.getColumnCount(); //创建集合对象 ArrayList<T> list = new ArrayList<T>(); while(resultSet.next()){ T t = clazz.newInstance(); //处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for(int i = 0;i < columnCount;i++){ //获取列值 Object columnValue = resultSet.getObject(i + 1); //获取每个列的列名或者列的别名 String columnLabel = metaData.getColumnLabel(i + 1); //根据列名找到类中对应的属性 Field field = clazz.getDeclaredField(columnLabel); //将此对象的 accessible标志设置为true。 field.setAccessible(true); //将列值赋值给对应的对象的对应属性 field.set(t, columnValue); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); } finally{ JDBCUtils.closeResource(ps, conn, resultSet); } return null; }
PreparedStatement操作Blob类型的变量
-
PreparedStatement可以操作Blob类型的变量。
-
写入操作的方法:setBlob(InputStream is);
-
读取操作的方法:
Blob blob = getBlob(int index);
InputStream is = blob.getBinaryStream(); -
具体的insert
//向customers表中插入一条blob类型的数据 @Test public void testInsert() throws Exception{ Connection conn = JDBCUtils.getConnection(); String sql = "insert into customers(name,email,birth,photo) values(?,?,?,?)"; PreparedStatement ps = conn.prepareStatement(sql); ps.setObject(1, "mama"); ps.setObject(2, "mama@qq.com"); ps.setObject(3, "2200-12-31"); InputStream is = new FileInputStream(new File("a.jpg")); ps.setBlob(4, is); ps.execute(); JDBCUtils.closeResource(ps, conn); }
-
具体的query
//查询customers表中Blob类型的数据 @Test public void testQuery() { Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; InputStream is = null; FileOutputStream fos = null; try { conn = JDBCUtils.getConnection(); String sql = "select id,name,email,birth,photo from customers where id = ?"; ps = conn.prepareStatement(sql); ps.setObject(1, 22); rs = ps.executeQuery(); if(rs.next()){ //方式一: // int id = rs.getInt(1); // String name = rs.getString(2); // String email = rs.getString(3); // Date birth = rs.getDate(4); //方式二: int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); Date birth = rs.getDate("birth"); Customer cust = new Customer(id, name, email, birth); System.out.println(cust); //将Blob类型的字段下载下来,以文件的方法保存在本地 Blob photo = rs.getBlob("photo"); is = photo.getBinaryStream(); fos = new FileOutputStream("2.jpg"); byte[] buffer = new byte[1024]; int len; while((len = is.read(buffer)) != -1){ fos.write(buffer,0,len); } } } catch (Exception e) { e.printStackTrace(); } finally{ try { if(is != null) is.close(); if(fos != null) fos.close(); } catch (IOException e) { e.printStackTrace(); } JDBCUtils.closeResource(ps, conn, rs); } }
-
注意:如果在制定了相关的Blob类型以后,还报错:xxx too large,那么在mysql的安装目录下,找到my.ini文件,加上如下配置参数:
max_allowed_packet=16MB
同时注意:修改了my.ini文件之后,需要重新启动mysql服务。
-
MySQL的四种BLOB类型(除了在存储的最大信息量上不同外,他们是等同的)
- TinyBlob 255字节
- Blob 65K字节
- MediumBlob 16M字节
- LongBlob 4G字节
-
-
PreparedStatement实现高效的批量插入
- 层次一:使用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.execute(sql);
}
-
层次二:使用PreparedStatement替换Statement
上面已经提到了,此处省略。 -
层次三:
-
addBatch()、executeBatch()、clearBatch()
-
mysql服务器默认是关闭批处理的,我们需要通过一个参数,让mysql开启批处理的支持。
?rewriteBatchedStatements=true
写在配置文件的url后面
-
使用更新的mysql 驱动:mysql-connector-java-5.1.37-bin.jar
-
层次四:设置连接不允许自动提交数据
-
最终版的代码体现
@Test public void insertTest4() { Connection conn = null; PreparedStatement ps = null; try { conn = JDBCUtils.getConnection(); //设置不允许自动提交数据 conn.setAutoCommit(false); String sql = "insert into goods(name) values(?)"; ps = conn.prepareStatement(sql); for (int i = 1; i <= 1000000; i++) { ps.setObject(1,"name_" + i); //1.“攒”sql ps.addBatch(); if(i % 500 == 0){ //2.执行batch ps.executeBatch(); //3.清空batch ps.clearBatch(); } } //提交数据 conn.commit(); } catch (Exception e) { e.printStackTrace(); } finally{ JDBCUtils.closeResource(ps, conn); } }
总结:PreparedStatement与Statement的异同?
- 指出二者的关系? 接口 与 子接口的关系
- 开发中,PreparedStatement替换Statement
- An object that represents a precompiled SQL statement,预编译SQL语句
- 代码的可读性和可维护性。
- PreparedStatement 能最大可能提高性能:
- DBServer会对预编译语句提供性能优化。因为预编译语句有可能被重复调用,所以语句在被DBServer的编译器编译后的执行代码被缓存下来,那么下次调用时只要是相同的预编译语句就不需要编译,只要将参数直接传入编译过的语句执行代码中就会得到执行。
- 在statement语句中,即使是相同操作但因为数据内容不一样,所以整个语句本身不能匹配,没有缓存语句的意义.事实是没有数据库会对普通语句编译后的执行代码缓存。这样每执行一次都要对传入的语句编译一次。
- (语法检查,语义检查,翻译成二进制命令,缓存)
- PreparedStatement 可以防止 SQL 注入
数据库的事务
-
什么叫数据库事务
- 一组逻辑操作单元,使数据从一种状态变换到另一种状态。
- 一组逻辑操作单元:一个或多个DML操作
-
事务处理的原则
- 保证所有事务都作为一个工作单元执行,即使出现了故障,都不能改变这种执行方式。当在一个事务中执行多个操作时,要么所有的事务都被提交(commit),那么这些修改就永久的保存下来;要么数据库管理系统将放弃所做的所有修改,整个事务**回滚(rollback)**到最初的状态。
- 说明:
- 数据一旦提交,就不可回滚。
- 哪些操作会导致数据库自动提交数据
- DDL操作一旦执行,都会自动提交。(set autocommit = false对DDL操作失效)
- DML默认情况下,一旦执行,就会自动提交。(我们可以通过set autocommit = false的方式取消DML操作的自动提交)
- 默认在关闭连接的时候会自动提交数据
-
代码实现
@Test public void testUpdateWithTransaction() { Connection conn = null; try { conn = JDBCUtils.getConnection(); // System.out.println(conn.getAutoCommit()); //true //1.取消数据的自动提交功能 conn.setAutoCommit(false); 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"); System.out.println("转账成功!"); //2.提交数据 conn.commit(); } catch (Exception e) { e.printStackTrace(); //3.回滚数据 try { conn.rollback(); } catch (SQLException e1) { e1.printStackTrace(); } } finally { try { //修改其为自动提交数据 //主要针对于使用数据库连接池的情况 conn.setAutoCommit(true); } catch (SQLException e) { e.printStackTrace(); } JDBCUtils.closeResource(null, conn); } }
-
考虑到事务以后,实现的通用的增删改操作:
// 通用的增删改操作---version 2.0(考虑事务) public int update(Connection conn,String sql, Object... args) { // sql中占位符的个数,应该与可变参的长度相同 PreparedStatement ps = null; try { // 1.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]);// 小心参数声明错误!! } // 3.执行 return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { // 4.资源的关闭 JDBCUtils.closeResource(ps, null); } return 0; }
-
考虑到事务以后,实现的通用的查询: version 2.0
//通用的查询操作,用于返回数据表中的一条记录(version2.0,考虑上事务) public <T> T getInstance(Connection conn,Class<T> clazz,String sql,Object... args) { PreparedStatement ps = null; ResultSet resultSet = null; try { //1.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); //2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } //3.执行,获取结果集 resultSet = ps.executeQuery(); //4.获取结果集的元数据ResultSetMetaData ResultSetMetaData metaData = resultSet.getMetaData(); //通过元数据获取结果集的列数 int columnCount = metaData.getColumnCount(); if(resultSet.next()){ T t = clazz.newInstance(); //处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for(int i = 0;i < columnCount;i++){ //获取列值 Object columnValue = resultSet.getObject(i + 1); //获取每个列的列名或者列的别名 String columnLabel = metaData.getColumnLabel(i + 1); //根据列名找到类中对应的属性 Field field = clazz.getDeclaredField(columnLabel); //将此对象的 accessible标志设置为true。 field.setAccessible(true); //将列值赋值给对应的对象的对应属性 field.set(t, columnValue); } return t; } } catch (Exception e) { e.printStackTrace(); } finally{ JDBCUtils.closeResource(ps, null, resultSet); } return null; }
事务的属性
- 四大属性(ACID)
-
原子性(Atomicity)
原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。 -
一致性(Consistency)
事务必须使数据库从一个一致性状态变换到另外一个一致性状态。 -
隔离性(Isolation)
事务的隔离性是指一个事务的执行不能被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。 -
持久性(Durability)
持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来的其他操作和数据库故障不应该对其有任何影响。
-
- 数据操作过程中可能出现的问题:(针对隔离性)
- 对于同时运行的多个事务, 当这些事务访问数据库中相同的数据时, 如果没有采取必要的隔离机制, 就会导致各种并发问题:
- 脏读: 对于两个事务 T1, T2, T1 读取了已经被 T2 更新但还没有被提交的字段。之后, 若 T2 回滚, T1读取的内容就是临时且无效的。
- 不可重复读: 对于两个事务T1, T2, T1 读取了一个字段, 然后 T2 更新了该字段。之后, T1再次读取同一个字段, 值就不同了。
- 幻读: 对于两个事务T1, T2, T1 从一个表中读取了一个字段, 然后 T2 在该表中插入了一些新的行。之后, 如果 T1 再次读取同一个表, 就会多出几行。
-
数据库的四种隔离级别:(一致性和并发性:一致性越好,并发性越差)
- READ UNCOMMITTED(读未提交数据):允许事务读取未被其他事务提交的变更,脏读、不可重复读和幻读的问题都会出现
- READ COMMITTED(读已提交数据):只允许事务读取已经被其他事务提交的变更,可以避免脏读,但不可重复度和幻读问题仍然可能出现
- REPEATABLE READ(可重复读):确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读,但幻读的问题仍然存在
- SERIALIZABLE(串行化):确保事务可以从一个表中读取相同的行,在这个事务持续期间,禁止其他事务对该表执行插入、更新和删除操作,所有并发问题都可以避免,但性能十分低下
- 不同数据库支持的隔离级别:
- Oracle支持的2种事务隔离级别:READ COMMITTED、SERIALIZABLE。Oracle默认的事务隔离级别为:READ COMMITTED
- Mysql支持4种事务隔离级别。Mysql默认的事务隔离级别为:REPEATABLE READ
-
如何查看并设置隔离级别
-
查看当前的隔离级别:
select @@tx_isolation;
-
设置当前Mysql连接的隔离级别:
set transaction isolation level read committed;
-
设置数据库系统的全局的隔离级别:
set global transaction isolation level read committed;
-
DAO及其子类
-
BaseDAO
/* * DAO:data(base) access object * 封装了针对于数据表的通用操作 */ public abstract class BaseDAO<T> { private Class<T> clazz = null; // public BaseDAO(){ // // } { //获取BaseDAO的子类继承的父类中的泛型 Type genericSuperclass = this.getClass().getGenericSuperclass(); ParameterizedType paramType = (ParameterizedType)genericSuperclass; Type[] typeArguments = paramType.getActualTypeArguments(); //获取了父类的泛型参数 clazz = (Class<T>) typeArguments[0]; //泛型的第一个参数 } // 通用的增删改操作---version 2.0(考虑事务) public int update(Connection conn, String sql, Object... args) { // sql中占位符的个数,应该与可变参的长度相同 PreparedStatement ps = null; try { // 1.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]);// 小心参数声明错误!! } // 3.执行 return ps.executeUpdate(); } catch (Exception e) { e.printStackTrace(); } finally { // 4.资源的关闭 JDBCUtils.closeResource(ps, null); } return 0; } // 通用的查询操作,用于返回数据表中的一条记录(version2.0,考虑上事务) public T getInstance(Connection conn, String sql, Object... args) { PreparedStatement ps = null; ResultSet resultSet = null; try { // 1.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 3.执行,获取结果集 resultSet = ps.executeQuery(); // 4.获取结果集的元数据ResultSetMetaData ResultSetMetaData metaData = resultSet.getMetaData(); // 通过元数据获取结果集的列数 int columnCount = metaData.getColumnCount(); if (resultSet.next()) { T t = clazz.newInstance(); // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for (int i = 0; i < columnCount; i++) { // 获取列值 Object columnValue = resultSet.getObject(i + 1); // 获取每个列的列名或者列的别名 String columnLabel = metaData.getColumnLabel(i + 1); // 根据列名找到类中对应的属性 Field field = clazz.getDeclaredField(columnLabel); // 将此对象的 accessible标志设置为true。 field.setAccessible(true); // 将列值赋值给对应的对象的对应属性 field.set(t, columnValue); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(ps, null, resultSet); } return null; } // 通用的查询操作,用于返回数据表中的多条记录构成的集合(version2.0,考虑上事务) public List<T> getForList(Connection conn, String sql, Object... args) { PreparedStatement ps = null; ResultSet resultSet = null; try { // 1.预编译sql语句,返回PreparedStatement的实例 ps = conn.prepareStatement(sql); // 2.填充占位符 for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } // 3.执行,获取结果集 resultSet = ps.executeQuery(); // 4.获取结果集的元数据ResultSetMetaData ResultSetMetaData metaData = resultSet.getMetaData(); // 通过元数据获取结果集的列数 int columnCount = metaData.getColumnCount(); // 创建集合对象 ArrayList<T> list = new ArrayList<T>(); while (resultSet.next()) { T t = clazz.newInstance(); // 处理结果集一行数据中的每一个列:给t对象指定的属性赋值 for (int i = 0; i < columnCount; i++) { // 获取列值 Object columnValue = resultSet.getObject(i + 1); // 获取每个列的列名或者列的别名 String columnLabel = metaData.getColumnLabel(i + 1); // 根据列名找到类中对应的属性 Field field = clazz.getDeclaredField(columnLabel); // 将此对象的 accessible标志设置为true。 field.setAccessible(true); // 将列值赋值给对应的对象的对应属性 field.set(t, columnValue); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(ps, null, resultSet); } return null; } //用于查询特殊值的通用的方法 public <E> E getValue(Connection conn,String sql,Object... args) { PreparedStatement ps = null; ResultSet rs = null; try { ps = conn.prepareStatement(sql); for (int i = 0; i < args.length; i++) { ps.setObject(i + 1, args[i]); } rs = ps.executeQuery(); if(rs.next()) { return (E)rs.getObject(1); } } catch (SQLException e) { e.printStackTrace(); } finally { JDBCUtils.closeResource(ps, null, rs); } return null; } }
-
CustomerDAO
/* * 此接口用于规范针对于customers表的常用操作 */ public interface CustomerDAO { /** * @Description 将cust对象添加到数据库中 * @param conn * @param cust */ void insert(Connection conn,Customer cust); /** * @Description 根据指定的id,删除表中的一条记录 * @param conn * @param id */ void deleteById(Connection conn,int id); /** * @Description 针对于内存中的cust对象,去修改数据表中指定的记录 * @param conn * @param cust */ void update(Connection conn,Customer cust); /** * @Description 根据指定的id查询得到对应的Customer对象 * @param conn * @param id * @return */ Customer getCustomerById(Connection conn,int id); /** * @Description 查询表中的所有记录构成的集合 * @param conn * @return */ List<Customer> getAll(Connection conn); /** * @Description 返回数据表中数据的条目数 * @param conn * @return */ Long getCount(Connection conn); /** * @Description 返回数据表中最大的生日 * @param conn * @return */ Date getMaxBirth(Connection conn); }
-
CustomerDAOImpl
public class CustomerDAOImpl extends BaseDAO<Customer> implements CustomerDAO{ @Override public void insert(Connection conn, Customer cust) { String sql = "insert into customers(name,email,birth) values(?,?,?)"; update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth()); } @Override public void deleteById(Connection conn, int id) { String sql = "delete from customers where id = ?"; update(conn,sql,id); } @Override public void update(Connection conn, Customer cust) { String sql = "update customers set name = ?,email = ?,birth = ? where id = ?"; update(conn,sql,cust.getName(),cust.getEmail(),cust.getBirth(),cust.getId()); } @Override public Customer getCustomerById(Connection conn, int id) { String sql = "select id,name,email,birth from customers where id = ?"; Customer customer = getInstance(conn,sql,id); return customer; } @Override public List<Customer> getAll(Connection conn) { String sql = "select id,name,email,birth from customers"; List<Customer> list = getForList(conn,sql); return list; } @Override public Long getCount(Connection conn) { String sql = "select count(*) from customers"; return getValue(conn, sql); } @Override public Date getMaxBirth(Connection conn) { String sql = "select max(birth) from customers"; return getValue(conn, sql); } }
-
总结:考虑到事务以后的数据库操作(重点)
- 获取数据库的连接
Connection conn = JDBCUtils.getConnection(); //方式1:手动获取连接 方式2:数据库连接池
conn.setAutoCommit(false); //体现事务
- 如下的多个DML操作,作为一个事务出现:
操作1:需要使用通用的增删改查操作 //通用的增删改查操作如何实现?
//方式1:手动使用PreparedStatement实现
操作2:需要使用通用的增删改查操作 //方式2:使用dbutils.jar中QueryRunner类 操作3:需要使用通用的增删改查操作
conn.commit();
-
如果出现异常,则:
conn.rollback(); -
关闭资源
JDBCUtils.closeResource(…,…,…); //方式1:手动关闭资源 方式2:DbUtils类的关闭方法
数据库连接池
-
在使用开发基于数据库的web程序时,传统的模式基本是按以下步骤:
- 在主程序(如servlet、beans)中建立数据库连接
- 进行sql操作
- 断开数据库连接
-
传统连接的问题
- 普通的JDBC数据库连接使用 DriverManager 来获取,每次向数据库建立连接的时候都要将 Connection 加载到内存中,再验证用户名和密码(得花费0.05s~1s的时间)。需要数据库连接的时候,就向数据库要求一个,执行完成后再断开连接。这样的方式将会消耗大量的资源和时间。**数据库的连接资源并没有得到很好的重复利用。**若同时有几百人甚至几千人在线,频繁的进行数据库连接操作将占用很多的系统资源,严重的甚至会造成服务器的崩溃。
- **对于每一次数据库连接,使用完后都得断开。**否则,如果程序出现异常而未能关闭,将会导致数据库系统中的内存泄漏,最终将导致重启数据库。(回忆:何为Java的内存泄漏?)
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾及的分配出去,如连接过多,也可能导致内存泄漏,服务器崩溃。
-
如何解决传统开发中的数据库连接问题:使用数据库连接池
-
使用数据库连接池的好处
1. 资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
2. 更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
3. 新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
4. 统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
或者说大白话:
- 提高程序的响应速度(减少了创建连接需要的时间)
- 降低资源的消耗(可以重复使用已经提供好的连接)
- 便于连接的管理
-
实现的方式
-
DBCP 是Apache提供的数据库连接池。tomcat 服务器自带dbcp数据库连接池。速度相对c3p0较快,但因自身存在BUG,Hibernate3已不再提供支持。
-
C3P0 是一个开源组织提供的一个数据库连接池,**速度相对较慢,稳定性还可以。**hibernate官方推荐使用
-
Proxool 是sourceforge下的一个开源项目数据库连接池,有监控连接池状态的功能,稳定性较c3p0差一点
-
BoneCP 是一个开源组织提供的数据库连接池,速度快
-
Druid 是阿里提供的数据库连接池,据说是集DBCP 、C3P0 、Proxool 优点于一身的数据库连接池,但是速度不确定是否有BoneCP快
-
C3P0数据库连接池
-
导入jar包
c3p0-0.9.1.2.jar
-
测试连接的代码
/** * * @Description 使用C3P0的数据库连接池技术 * @return * @throws SQLException */ //数据库连接池只需提供一个即可。 private static ComboPooledDataSource cpds = new ComboPooledDataSource("hellc3p0"); public static Connection getConnection1() 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"> <!-- 提供获取连接的四个基本信息 --> <property name="driverClass">com.mysql.jdbc.Driver</property> <property name="jdbcUrl">jdbc:mysql://localhost:3306/test</property> <property name="user">root</property> <property name="password">root</property> <!-- 进行数据库连接池管理的基本信息 --> <!-- 当数据库连接池中的连接数不够时,c3p0一次性向数据库服务器申请的连接数 --> <property name="acquireIncrement">5</property> <!-- c3p0数据库连接池中初始化时的连接数 --> <property name="initialPoolSize">10</property> <!-- c3p0数据库连接池中维护的最少的连接数 --> <property name="minPoolSize">10</property> <!-- c3p0数据库连接池中维护的最多的连接数 --> <property name="maxPoolSize">100</property> <!-- c3p0数据库连接池中最多维护的Statement的个数 --> <property name="maxStatements">50</property> <!-- 每个连接中可以最多使用的Statement的个数 --> <property name="maxStatementsPerConnection">2</property> </named-config> </c3p0-config>
DBCP数据库连接池
-
导入jar包
- commons-dbcp-1.4.jar
- commons-pool-1.5.5.jar
-
测试连接的代码
/** * * @Description 使用DBCP数据库连接池技术获取数据库连接 * @return * @throws Exception */ //创建一个DBCP数据库连接池 private static DataSource source; static{ try { Properties pros = new Properties(); FileInputStream is = new FileInputStream(new File("src/dbcp.properties")); pros.load(is); source = BasicDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection2() throws Exception{ Connection conn = source.getConnection(); return conn; }
-
配置文件在src下。名为:dbcp.properties
driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/test username=root password=root initialSize=10
Druid(德鲁伊)数据库连接池
-
导入jar包
druid-1.1.10.jar
-
测试连接的代码
/** * 使用Druid数据库连接池技术 */ private static DataSource source1; static{ try { Properties pros = new Properties(); InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("druid.properties"); pros.load(is); source1 = DruidDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection3() throws SQLException{ Connection conn = source1.getConnection(); return conn; }
-
配置文件在src下。名为:druid.properties
url=jdbc:mysql://localhost:3306/test username=root password=root driverClassName=com.mysql.jdbc.Driver initialSize=10 maxActive=10
DBUtils提供的jar包实现CRUD操作
-
导入jar包
commons-dbutils-1.3.jar
-
使用现成的jar中的QueryRunner测试增、删、改的操作:
//测试插入 @Test public void testInsert() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils.getConnection3(); 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 e) { e.printStackTrace(); }finally{ JDBCUtils.closeResource(conn, null); } }
-
使用现成的jar中的QueryRunner测试查询的操作:
//测试查询 /* * BeanHander:是ResultSetHandler接口的实现类,用于封装表中的一条记录。 */ @Test public void testQuery1(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); 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); } catch (SQLException e) { // TODO Auto-generated catch block e.printStackTrace(); }finally{ JDBCUtils.closeResource(conn, null); } } /* * BeanListHandler:是ResultSetHandler接口的实现类,用于封装表中的多条记录构成的集合。 */ @Test public void testQuery2() { Connection conn = null; try { QueryRunner runner = new QueryRunner(); 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); } catch (SQLException e) { e.printStackTrace(); }finally{ JDBCUtils.closeResource(conn, null); } } /* * MapHander:是ResultSetHandler接口的实现类,对应表中的一条记录。 * 将字段及相应字段的值作为map中的key和value */ @Test public void testQuery3(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils.getConnection3(); 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 (SQLException e) { e.printStackTrace(); }finally{ JDBCUtils.closeResource(conn, null); } } /* * MapListHander:是ResultSetHandler接口的实现类,对应表中的多条记录。 * 将字段及相应字段的值作为map中的key和value。将这些map添加到List中 */ @Test public void testQuery4(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils.getConnection3(); String sql = "select id,name,email,birth from customers where id < ?"; MapListHandler handler = new MapListHandler(); List<Map<String, Object>> list = runner.query(conn, sql, handler, 23); list.forEach(System.out::println); } catch (SQLException e) { e.printStackTrace(); }finally{ JDBCUtils.closeResource(conn, null); } } /* * ScalarHandler:用于查询特殊值 */ @Test public void testQuery5(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils.getConnection3(); 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(); }finally{ JDBCUtils.closeResource(conn, null); } } @Test public void testQuery6(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); conn = JDBCUtils.getConnection3(); String sql = "select max(birth) from customers"; ScalarHandler handler = new ScalarHandler(); Date maxBirth = (Date) runner.query(conn, sql, handler); System.out.println(maxBirth); } catch (SQLException e) { e.printStackTrace(); }finally{ JDBCUtils.closeResource(conn, null); } } /* * 自定义ResultSetHandler的实现类 */ @Test public void testQuery7(){ Connection conn = null; try { QueryRunner runner = new QueryRunner(); 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 null; // return new Customer(12, "成龙", "Jacky@126.com", new Date(234324234324L)); if(rs.next()){ int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); Date birth = rs.getDate("birth"); Customer customer = new Customer(id, name, email, birth); return customer; } return null; } }; Customer customer = runner.query(conn, sql, handler,23); System.out.println(customer); } catch (SQLException e) { e.printStackTrace(); }finally{ JDBCUtils.closeResource(conn, null); } }
-
使用dbutils.jar包中的DbUtils工具类实现连接等资源的关闭:
/** * * @Description 使用dbutils.jar中提供的DbUtils工具类,实现资源的关闭 * @param conn * @param ps * @param rs */ public static void closeResource1(Connection conn,Statement ps,ResultSet rs){ // try { // DbUtils.close(conn); // } catch (SQLException e) { // e.printStackTrace(); // } // try { // DbUtils.close(ps); // } catch (SQLException e) { // e.printStackTrace(); // } // try { // DbUtils.close(rs); // } catch (SQLException e) { // e.printStackTrace(); // } DbUtils.closeQuietly(conn); DbUtils.closeQuietly(ps); DbUtils.closeQuietly(rs); }