JDBC学习笔记
JDBC学习笔记
一. 前阶段
0.0前章
-
HTML CSS JS 负责结构,表现,行为
-
服务端Tomcat 有关的XML语言(可拓展性) ,可以自定义标签,用于写配置文件的
-
服务器Tomcat的组件Servlet 作用:写java代码,用于交互用户
- 获取用户的请求参数
- 处理请求,如注册,登录,查找数据
- 响应请求
- JSP的由来:可以替代Servlet的响应请求,由于Servlet本可以做的(页面展示),但是Servlet更多做的事是处理逻辑层面的东西。JSP用于页面的动态显示
- EL和JSTL的由来:
- 为了JSP有更好的开发效率
- JSTL用于JSP脚本片段
- Cookie和Session的由来:
- 为了让服务器端辨别浏览器端的二次开启是同一个东西,避免重新开启一个界面又要登录。
-
Tomcat中三大服务器组件:Servlet Filter Listener
-
Ajax :
- JS里的组件
- 实现异步请求举例:注册时,光标在昵称时打字提示已被占用;可以用于地图的滑动加载出信息
- JSON:轻量级字段
-
将一些数据从服务器以文本形式发送给浏览器
第1章:概述
- JDBC提供了统一的功能访问数据库
-
JDBC驱动:实现JDBC规则,把抽象的接口具体化
-
JDBC是sun公司提供一套用于数据库操作的接口,java程序员只需要面向这套接口编程即可。不同的数据库厂商,需要针对这套接口,提供不同的实现。不同的实现的集合,就是不同数据库的驱动。 --------面向接口编程
1.0连接
//最终版:将数据库连接需要的4个基本信息声明在配置文件中,只需要在配置文件中修改表信息就好 public class ConnectionTest { @Test public void getConnection5() throws Exception { //1.读取配置文件中的4个基本信息 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 driverClass = pros.getProperty("driverClass"); //2.加载驱动 Class.forName(driverClass); //3.获取连接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); } }
#配置文件,一般命名格式为jdbc.XX xx:类加载器的名字 user=root password=password url=jdbc:mysql://localhost:3306/jdbc driverClass=com.mysql.cj.jdbc.Driver
运行:表示连接成功
com.mysql.cj.jdbc.ConnectionImpl@25359ed8
可能会出现:java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
原因:jar包没导好;
解决:
可能会出现:空指针异常,说明还没配置到Liberal下
2.0 通用的连接工具类
public class JDBCUtils { //1.获取连接 public static Connection getConnection()throws Exception{ //1.读取配置文件中的4个基本信息 InputStream is = JDBCUtils.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 driverClass = pros.getProperty("driverClass"); //2.加载驱动 Class.forName(driverClass); //3.获取连接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); return conn; } //2.关闭资源 public static void closeResourse(Connection conn, Statement ps){ try { if (conn!=null) conn.close(); if (ps!=null) ps.close(); } catch (SQLException e) { throw new RuntimeException(e); } } //3.关闭查询资源 public static void closeResourse(Connection conn, Statement ps, ResultSet resultSet){ try { if (conn!=null) conn.close(); if (ps!=null) ps.close(); if (resultSet!=null) resultSet.close(); } catch (SQLException e) { throw new RuntimeException(e); } } //4.druid数据库连接池技术 private static DataSource source; static { try { Properties pros = new Properties(); InputStream is = ClassLoader.getSystemClassLoader(). getResourceAsStream("druid.properties"); pros.load(is); source = DruidDataSourceFactory.createDataSource(pros); } catch (Exception e) { e.printStackTrace(); } } public static Connection getDruidConnection() throws SQLException { Connection conn = source.getConnection(); return conn; } }
/** * @ author Guo daXia * @ create 2022/10/30 *JDBCUtils工具类 */ public class JDBCUtils { //1.获取连接 public static Connection getConnection()throws Exception{ //1.读取配置文件中的4个基本信息 InputStream is = JDBCUtils.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 driverClass = pros.getProperty("driverClass"); //2.加载驱动 Class.forName(driverClass); //3.获取连接 Connection conn = DriverManager.getConnection(url, user, password); System.out.println(conn); return conn; } //2.关闭修改资源 public static void closeResourse(Connection conn, Statement ps){ try { if (conn!=null) conn.close(); if (ps!=null) ps.close(); } catch (SQLException e) { throw new RuntimeException(e); } } //3.关闭查询资源 public static void closeResourse(Connection conn, Statement ps, ResultSet resultSet){ try { if (conn!=null) conn.close(); if (ps!=null) ps.close(); if (resultSet!=null) resultSet.close(); } catch (SQLException e) { throw new RuntimeException(e); } } }
3.0通用的修改方法及使用
在2.0中,我们使用工具类,将连接包装成方法,资源的关闭包装成方法,现在我们可以试着写出5步走对数据库进行修改,这里我们先写出6步框架,然后运用工具类,写出通用的增删改操作。
为什么方法参数是这两个呢?
- 具体操作的sql语句不相同
- 占位符个数的不相同
所以,可以作为参数传递不同的sql语句和占位符内容
//通用的增删改 public void update(String sql,Object ...args) {//sql占位符的个数=可变性惨个数 Connection conn = null; //不确定: PreparedStatement ps = null; //sql语句 try { //占位符的个数 //1.获取数据库的连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatemnet的实例 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.closeResourse(conn,ps); } }
该具体操作是:对数据库中删除表customers中id为30的一条数据。
@Test public void testUpdate(){ String sql="delete from customers where id =?"; update(sql,30); }
那么,可不可以修改其他表的一条数据呢?可以的。
@Test public void testUpdate(){ String sql = "update `order` set order_name = ? where order_id = ?"; update(sql,"DD","2"); }
4.0通用的查询方法
4.01实现一个表的查询(初版)
- 引入:我们先写一个查询数据表中的一张表customer的一条数据,在这里我们会发现有些步骤跟3.0写修改方法一样,如连接数据库使用到JDBCUtils工具类,需要预编译sql语句,放回一个PreparedStatement对象,填充占位符,执行,关闭资源;不过你会发现,执行的方法不同,该executeQuery()将会得到一个结果集,我们要获取该结果集中每个字段值,即数据库的列值,然后进行输出,这里采用javaORM思想实现。
@Test public void TestQuery() { Connection conn = null; PreparedStatement ps = null; ResultSet resultSet = null; try { conn = getConnection(); String sql = "select id,name,email,birth from customers where id = ?"; ps = conn.prepareStatement(sql); ps.setObject(1,31); //执行 resultSet = ps.executeQuery(); //处理结果集 if (resultSet.next()){ //判断结果集的下一条是否有数据,如果有则返回true,指针下移;如果没有则返回false,指针不移动。 //获取当前这条数据的所有字段值 int id = resultSet.getInt(1); String name = resultSet.getString(2); String email = resultSet.getString(3); Date date = resultSet.getDate(4); //将数据封装成一个对象 Customers customer = new Customers(id,name,email,date); System.out.println(customer); } } catch (Exception e) { e.printStackTrace(); } finally { //关闭资源 JDBCUtils.closeResourse(conn,ps,resultSet); } }
/** * @ author Guo daXia * @ create 2022/10/30 */ public class Customers { //联通结果集数据 //ORM编程思想:Object Relational Mapping //一个数据表-->java类 //表中一条记录-->java对象 //表中一个字段-->java属性 private int id; private String name; private String email; private Date date; public Customers() {} public Customers(int id, String name, String email, Date date) { this.id = id; this.name = name; this.email = email; this.date = date; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } @Override public String toString() { return "Customers{" + "id=" + id + ", name='" + name + '\'' + ", email='" + email + '\'' + ", date=" + date + '}'; } }
4.0.2实现固定表的通用查询(中版)
- 我们已经尝试了获取表中的一条操作的,但因为每次查询的语句不同,占位符的不确定,所有需要一个通用的方法实现查询表的数据。
//针对于customer表的通用操作 public Customers queryForCustomers(String sql,Object ...args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs= null; try { conn = getConnection(); ps = conn.prepareStatement(sql); for (int i =0;i<args.length;i++){ ps.setObject(i+1,args[i]); } rs = ps.executeQuery(); //获取结果集中的元数据:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //通过结果集中getColumnCount()方法获取结果集中的列数 int columnCount = rsmd.getColumnCount(); if (rs.next()){ Customers cust = new Customers(); for (int i =0;i<columnCount;i++){ //获取列值 Object columValue = rs.getObject(i+1); //获取每个列的列名 String columName = rsmd.getColumnLabel(i+1); //给cust对象指定的columName属性,赋值为columValue:通过反射 Field field = Customers.class.getDeclaredField(columName); field.setAccessible(true); field.set(cust,columValue); } return cust; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResourse(conn,ps,rs); } return null; }
重点:
(一).以上方法使用到了:
- 元数据的获取
- 通过反射给某一对象指定的某一属性赋某值
(二).getColumLaber()替换getColumnName():
- 因为通过ResultSetMetaData的方法getColumnName()获取的是确切的列名;而由ORM思想知道一个java属性对应一个数据库列名,如果属性名!=列名,会抛出异常:
NoSuchFieldException
- 所以解决办法
- 给sql语句起别名,别名为java的属性名
- 使用getColumLaber()替换getColumnName()
- 总结:推荐方法getColumLaber(),因为无论有没有给sql语句起别名,都能运行成功。
4.0.3实现指定表的通用查询(终版)
- 针对于customer表的通用操作已经了解了,现在是时候写最终版了;前面我们了解方法的返回类型就是一张表,这里可以使用泛型格式。
public <T> T getInstance(Class<T> clazz,String sql,Object.. args){}
说明:
sql:为sql语句,跟写数据库一样
args:占位符?,顾名思义就是有多少个?就有多少个要填的位置
- 在调用该方法时传递实参:
T t =getInstance(T.class,sql,1);
最终代码:
/** * @ author Guo daXia * @ create 2022/10/31 */ public class PreparedStatmentQuerydemo { @Test public void testGetInstance(){ String sql = "select id,name,email from customers where id = ?"; Customers cust = getInstance(Customers.class, sql,12); System.out.println(cust); } //针对于指定一张表的查询操作方法 public <T>T getInstance(Class<T> clazz,String sql,Object ...args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs= null; try { conn = getConnection(); ps = conn.prepareStatement(sql); for (int i =0;i<args.length;i++){ ps.setObject(i+1,args[i]); } 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 columValue = rs.getObject(i+1); //获取每个列的列名 String columName = rsmd.getColumnLabel(i+1); //给cust对象指定的columName属性,赋值为columValue:通过反射 Field field = Customers.class.getDeclaredField(columName); field.setAccessible(true); field.set(t,columValue); } return t; } } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResourse(conn,ps,rs); } //System.out.println("111"); return null; } }
4.0.4 实现指定范围的查询
- 在SQL中,可以查询指定表中id小于某一数的全部数据,这里也会实现以上
//针对于指定一张表的查询方法,返回多条查询语句的结果集 public <T> List<T> getForList(Class<T> clazz, String sql, Object ...args) { Connection conn = null; PreparedStatement ps = null; ResultSet rs= null; try { conn = getConnection(); ps = conn.prepareStatement(sql); for (int i =0;i<args.length;i++){ ps.setObject(i+1,args[i]); } rs = ps.executeQuery(); //获取结果集的元数据:ResultSetMetaData ResultSetMetaData rsmd = rs.getMetaData(); //通过ResultSetMetaData获取结果集中的列数 // 创建集合对象 ArrayList list = new ArrayList(); int columnCount = rsmd.getColumnCount(); while (rs.next()){ T t = clazz.newInstance(); for (int i =0;i<columnCount;i++){ //获取列值 Object columValue = rs.getObject(i+1); //获取每个列的列名 String columName = rsmd.getColumnLabel(i+1); //给cust对象指定的columName属性,赋值为columValue:通过反射 Field field = Customers.class.getDeclaredField(columName); field.setAccessible(true); field.set(t,columValue); } list.add(t); } return list; } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResourse(conn,ps,rs); } return null; }
@Test public void testGetInstance(){ String sql1 = "select id,name,email from customers where id <?"; List<Customers> list = getForList(Customers.class, sql1,5); list.forEach(System.out::println); }
总结:
- 返回值:LIst
- if改为while:指针下移循环查找多条sql语句
- list.forEach(Syst.out::println):输出查询到的所有
5.0 PreparedStatement的好处
- 把可变数据和不变数据分离的做法,也是我们学java要掌握的思想之一,提高代码利用率和内存效率,防止跟长冗余的代码,也提代码高安全性,自上课以来就一直在表达这个思想了
- 除了解决Statemnt的拼串,sql注入问题,还能操作Blob的数据;实现更高效率的批量操作
6.0 API小结:
- 两种思想:
- 面向接口编程思想
- ORM思想
sql是需要结合列名跟属性名来写的,注意起别名
- 两种技术:
- JDBC结果集的元数据:ResultSetMetaDate
- 获取列数:getColumnCount()
- 获取列的别名:getColumnLabel()
- 通过反射,创建指定类的对象,获取指定的属性并赋值
- JDBC结果集的元数据:ResultSetMetaDate
7.0向数据表插入Blob类型的字段
- 学到这里应该很容易操作了吧,那么我们再练练手,只不过这次是插入一张照片到数据库中。类型为Blob
public void testInsert() { Connection conn = null; PreparedStatement ps = null; try { //1.获取连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,并返回PreparedStatement String sql = "insert into customers(name,email,birth,photo) values (?,?,?,?)"; ps = conn.prepareStatement(sql); //3.填充占位符 ps.setObject(1,"郭大侠"); ps.setObject(2,"192@163.com"); ps.setObject(3,"2002-03-08"); FileInputStream is = new FileInputStream("work.jpg"); ps.setBlob(4,is); //4.执行 ps.execute(); } catch (Exception e) { e.printStackTrace(); } finally { //5.关闭资源 JDBCUtils.closeResourse(conn,ps); } }
8.0从数据表中读入Blob类型数据
/** * @ author Guo daXia * @ create 2022/10/31 */ public class BlobTest { //从数据表中读入Blob类型数据 //查询数据表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.setInt(1,32); rs = ps.executeQuery(); is = null; fos = null; if (rs.next()){ //结果集getXxx():传入列的别名 int id = rs.getInt("id"); String name = rs.getString("name"); String email = rs.getString("email"); Date birth = rs.getDate("birth"); Customers cust = new Customers(id, name, email, birth); System.out.println(cust); //将Blob类型的字段下载下来,保存在本地 Blob photo = rs.getBlob("photo"); is= photo.getBinaryStream(); fos = new FileOutputStream("guodaxia.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 { JDBCUtils.closeResourse(conn,ps,rs); try { if (is!=null) is.close(); } catch (IOException e) { throw new RuntimeException(e); } try { if (fos!=null) fos.close(); } catch (IOException e) { throw new RuntimeException(e); } } }
插入Blob类型的特殊情况说明:(出现异常:xxx too large)
- Blob默认情况下可以存储1M的图片,如果超过1M,则需要到配置文件里修改。
- 在my.ini文件加上如下配置参数:max_allowed_packet=16M。
- 修改完后,需要重启mysql服务。
9.0批量操作
- 要点:
- mysql服务器默认是关闭批量处理的,我们需要通过一个参数,然mysql开启批处理的支持,在配置文件的url后:
?rewriteBatchedStatements=true
- 将连接的自动提交数据关闭,在添加完后,再一次性提交数据
* 实现批量操作:关闭自动提交数据 * @ author Guo daXia * @ create 2022/10/31 */ public class BatchTest { //CREATE TABLE goods( //id INT PRIMARY KEY AUTO_INCREMENT, //NAME VARCHAR(25) //); @Test public void testBatch(){ //向goods表批量添加数据 Connection conn = null; PreparedStatement ps = null; try { conn = JDBCUtils.getConnection(); //设置不允许自动提交数据 conn.setAutoCommit(false); String sql = "insert into goods(name) values (?)"; ps = conn.prepareStatement(sql); long a =System.currentTimeMillis(); for (int i=1;i<=100000;i++){ ps.setObject(1,"name:"+i); //1.攒sql语句 ps.addBatch(); if (i%500==0){ //2.执行batch ps.executeBatch(); } //3.清空batch ps.clearBatch(); } //提交数据 conn.commit(); long b = System.currentTimeMillis(); System.out.println("花费的时间:"+(b-a)); } catch (Exception e) { e.printStackTrace(); } finally { JDBCUtils.closeResourse(conn,ps); } } }
二.后阶段
1 数据库事务
1.1数据库事务介绍
- 事务:一句逻辑操作单元,使数据从一种状态变换到另一种状态。
- 事务处理:也叫事务操作,用于保证所有事务都作为一个工作单位去执行,即使出现了故障也不能改变该执行方法;当在一个事务中执行了多个操作后,要么所有的事务都被提交(commit),那么这些修改就都被永久保存在电脑;要么数据库管理系统将放弃所完成的所有修改,整个事务回滚(rollback)到原始状态。
- 为保证数据库中的数据的一致性,数据的操作应当是离散的成组的逻辑单元;当它完成时,数据的一致性可以保持,而当这个单元中的一部分操作失败后,整个事务应全部视为错误,所有从起始点以后的操作应全部回退到开始状态。
1.2JDBC事务处理
/** * @ author Guo daXia * @ create 2022/10/31 */ public class testTransation { //针对于数据表user_table来说的: //AA给BB转账100元 @Test public void testUpdate() { String sql1 = "update user_table set balance = balance - 100 where user = ?"; update(sql1,"AA"); //模拟网络异常 System.out.println(100/0); String sql2 = "update user_table set balance = balance - 100 where user = ?"; update(sql2,"BB"); } //通用的增删改 public int update(String sql,Object ...args) {//sql占位符的个数=可变性惨个数 Connection conn = null; //不确定: PreparedStatement ps = null; //sql语句 try { //占位符的个数 //1.获取数据库的连接 conn = JDBCUtils.getConnection(); //2.预编译sql语句,返回PreparedStatemnet的实例 ps = conn.prepareStatement(sql); //3.填充占位符 for (int i=0;i< args.length;i++){ ps.setObject(i+1,args[i]);//小心数组索引越界 } //4.执行 return ps.executeUpdate();//返回执行多少次更新 } catch (Exception e) { e.printStackTrace(); } finally { //5.资源关闭 JDBCUtils.closeResourse(conn,ps); } return 0; } }
-
在上面代码中,模拟了转账操作,用代码层面的异常模拟网络通信异常,所以代码只会执行第一个sql语句。这是应该转钱不成功的,可是转钱者已经把钱都转出去了,问题是收钱者没有收到,导致冲突。
-
数据一旦提交后,就不可进行回滚。
-
数据什么时候意味着提交?
- 当一个连接对象被创建时,默认是自动提交事务:每次执行完一个sql语句时,如果执行成功,则会向数据库自动提交,不能回滚。
- DDL操作:一旦执行,便自动提交。
- DML操作:默认情况下,会自动提交。
- 但我们可以通过set autocommmit =false 的方式取消自动提交。
- 关闭数据库连接,数据就会被自动提交:如主动关闭数据库app界面。
- 当一个连接对象被创建时,默认是自动提交事务:每次执行完一个sql语句时,如果执行成功,则会向数据库自动提交,不能回滚。
-
所以我们要避免数据自动提交,实现数据回滚,把钱转回转账者。应该避免以上所有自动提交。
//****************************************考虑事务后的操作******************************** @Test public void testUpdate() { Connection conn = null; try { //0.建立连接 conn = JDBCUtils.getConnection(); //1.取消CML操作的自动提交 conn.setAutoCommit(false); String sql1 = "update user_table set balance = balance - 100 where user = ?"; update(conn,sql1,"AA"); //模拟网络异常 System.out.println(100/0); String sql2 = "update user_table set balance = balance - 100 where user = ?"; update(conn,sql2,"BB"); //2.提交数据 conn.commit(); } catch (Exception e) { e.printStackTrace(); //3.数据回滚 try { conn.rollback(); } catch (SQLException ex) { ex.printStackTrace(); } } finally { JDBCUtils.closeResourse(conn,null); } } //通用的增删改:2.0 version public int update(Connection conn, String sql,Object ...args) { PreparedStatement ps = null; try { //1.预编译sql语句,返回PreparedStatemnet的实例 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.closeResourse(null,ps); } return 0; }
总结:把连接放在外面,好比一条线,将多个DML串起来。因为事务可能有多个DML操作,如果其中有一个DML操纵执行失败,需要抛出异常并实现回滚:如果提交数据,没有回滚,会导致数据的遗失,即一方钱丢失。
1.3 事务的ACID属性
-
原子性(Atomicity)
-
一致性 (Consistency)
-
隔离性(Isolation)
-
持久性(Durability)
-
三级封锁协议作用:
-
用于解决修改丢失不可重复读和读脏数据问题,解决问题的焦点是给数据库对象何时加锁、加什么样的锁
-
一级封锁协议:事务T在修改数据R之前必须对其加X锁,直到事物结束时释放,解决修改丢失问题,但不解决不可重复读和读脏数据问题。
-
二级封锁协议:在一级封锁协议的基础上,事务T在读取数据R前,必须对其加S锁,读完后即可释放,解决读脏数据问题,但解决不了不可重复读问题。
-
三级封锁协议:在一级封锁协议的基础上,事务T在读取数据R前,必须对其加S锁,直到事务结束方可释放,解决不可重复读问题。
-
-
SAXS
2 数据库连接池
2.1 JDBC数据连接池的必要性
- 在使用开发基于数据库的web程序时,传统的模式基本是按以下三步:
-
- 在主程序中建立数据库连接
- 进行sql操作
- 断开数据库连接
-
- 这种开发模式,存在问题:
-
- 普通的JDBC数据库连接使用DriverManger获取,每次向数据库建立连接时都要将Connection加载到内存中,再验证用户名和密码正确性(需花费0.05s~1s不等的时间)。这种方式会消耗大量的资源跟时间。数据库的连接资源并没有得到很好的重复利用。
- 每一次数据库连接,使用完后都得断开。因为如果不断开会出现java的内存泄露。
- 何为jav的内存泄露:创建完的对象没有被正常回收。
- 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾忌的分配出去。
-
2.2 数据库连接池技术
- 该技术诞生源于解决数据库连接问题。
- 数据库连接池的基本思想:就是建立“缓冲池”。预先再缓冲池里放入一定数量的连接,当需要建立数据库连接时,才从“缓冲池”中取出一个,使用完后再放回去。
2.3 多种开源的数据库连接池
- JDBC的数据库连接池使用javax.sql.DataSource表示,DataSource只是一个接口,该接口通常有服务器(Weblogic、WebSphere、Tomcat)提供实现,也有一些开源组织提供了实现:
- DBCP是Apache提供的数据库连接池。tomcat服务器自带dbcp数据库连接池。速度相对c3p0较快,但因存在Bug,Hibernate3已不再提供实现;
- C3P0是一个开源组织提供的一个数据库连接池,速度慢,却稳定。Hibernate官方推荐。
- Druid是阿里提供的数据库连接池,据说是集DBCP、C3P0、Proxool优点于一身的数据库连接池。
- DataSource:通常被称为数据源,包括连接池和连接池管理两个部分,习惯上忽略后者。
- 用DataSource取代DriverManager来获取连接Connection。
2.4 连接池的代码实现
- 这里演练最常用的德鲁伊连接池
- 加载配置文件,命名为:druid.properties
url:jdbc:mysql:///test username=root password=password driverClassName=com.mysql.cj.jdbc.Driver initialSize=10 maxActive=10
- 实现连接
- 调用DruidDataSourceFactory的方法createDataSource()返回数据资源,然后通过资源调用连接
import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.pool.DruidDataSourceFactory; import org.junit.Test; import javax.sql.DataSource; import java.io.FileInputStream; import java.io.InputStream; import java.sql.Connection; import java.util.Properties; /** * @ author Guo daXia * @ create 2022/11/1 */ public class DruidTest { @Test public void getDruid() 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连接池的连接写JDBCUtils工具类中
已经放在JDBCUtils工具类了
3 Apache-DBUtils实现CRUD操作
-
是一个开源JDBC工具类库,对JDBC的简单封装。
-
该公司下的doc文档:Overview (Apache Commons DbUtils 1.7 API)
-
因为查询结果样式多样,如查询一条、多条、返回一个值、查询函数返回一个值,需要借助doc文档实现
-
接口:ResultSetHandler里的实现类都是处理查询要返回的具体内容操作
-
使用DBUtils类实现关闭资源
//使用DBUtils.jar中提供的DbUtils工具类,实现关闭资源 public static void closeResoured(Connection coon,Statement sp,ResultSet rs){ DbUtils.closeQuietly(conn); DbUtils.closeQuietly(sp); DbUtils.closeQuietly(rs); }
附:技巧
- IDEA导入jar包的步骤:
- 创建Directory文件lib;
- 进入alibaba的druid的下载地址:https://repo1.maven.org/maven2/com/alibaba/druid/。
- IDEA的Library添加配置。
- 出现Find JAR Web情况:
- 请在WEB-INF目录下创建lib文件夹
- 把jar包放进去
- project Structure 界面,左侧选择Libraries,点击加号,添加java Libraries。
- 弹出窗口选择WEB-INF文件夹下lib中的jar包,点击ok。
- ok之后弹出Choose Modules 窗口,选择相应module,ok。
- 当再次查看lib文件夹下jar包,有箭头指示时,表示jar包已经导入,可以使用。
- **出现:Library source does not match the bytecode for class **
-
方案一:IDEA 工具,点击File 》invalidate caches /restart,重启IDEA看是否解决问题。
-
方案二:重新构建项目,点击Build 》Rebuild Project,重新构建后看是否解决问题。
-
方案三:删除本地的jar包,删除.m2/resposity/XXX.jar,重新加载maven依赖,观察问题是否得到解决。
终极解决方案
经过深思熟虑,可能是Lombok插件的问题,Lombok插件不能清除之前的java类文件。解决方案,将Lombok禁用后重新启用,再重新加载maven依赖。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)