JDBC学习笔记

JDBC学习笔记

一. 前阶段

0.0前章

  1. HTML CSS JS 负责结构,表现,行为

  2. 服务端Tomcat 有关的XML语言(可拓展性) ,可以自定义标签,用于写配置文件的

  3. 服务器Tomcat的组件Servlet 作用:写java代码,用于交互用户

  • 获取用户的请求参数
  • 处理请求,如注册,登录,查找数据
  • 响应请求
  1. JSP的由来:可以替代Servlet的响应请求,由于Servlet本可以做的(页面展示),但是Servlet更多做的事是处理逻辑层面的东西。JSP用于页面的动态显示
  2. EL和JSTL的由来:
  • 为了JSP有更好的开发效率
  • JSTL用于JSP脚本片段
  1. Cookie和Session的由来:
  • 为了让服务器端辨别浏览器端的二次开启是同一个东西,避免重新开启一个界面又要登录。
  1. Tomcat中三大服务器组件:Servlet Filter Listener

  2. Ajax :

  • JS里的组件
  • 实现异步请求举例:注册时,光标在昵称时打字提示已被占用;可以用于地图的滑动加载出信息
  1. JSON:轻量级字段
  • 将一些数据从服务器以文本形式发送给浏览器

    第1章:概述

  1. JDBC提供了统一的功能访问数据库

  1. JDBC驱动:实现JDBC规则,把抽象的接口具体化

  2. 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;
}

重点:

(一).以上方法使用到了:

  1. 元数据的获取
  2. 通过反射给某一对象指定的某一属性赋某值

(二).getColumLaber()替换getColumnName():

  1. 因为通过ResultSetMetaData的方法getColumnName()获取的是确切的列名;而由ORM思想知道一个java属性对应一个数据库列名,如果属性名!=列名,会抛出异常:NoSuchFieldException
  2. 所以解决办法
  • 给sql语句起别名,别名为java的属性名
  • 使用getColumLaber()替换getColumnName()
  1. 总结:推荐方法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()
    • 通过反射,创建指定类的对象,获取指定的属性并赋值

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批量操作

  • 要点:
  1. mysql服务器默认是关闭批量处理的,我们需要通过一个参数,然mysql开启批处理的支持,在配置文件的url后:?rewriteBatchedStatements=true
  2. 将连接的自动提交数据关闭,在添加完后,再一次性提交数据
* 实现批量操作:关闭自动提交数据
* @ 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界面。
  • 所以我们要避免数据自动提交,实现数据回滚,把钱转回转账者。应该避免以上所有自动提交。

//****************************************考虑事务后的操作********************************
@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属性

  1. 原子性(Atomicity)

  2. 一致性 (Consistency)

  3. 隔离性(Isolation)

  4. 持久性(Durability)

  • 三级封锁协议作用:

    • 用于解决修改丢失不可重复读和读脏数据问题,解决问题的焦点是给数据库对象何时加锁、加什么样的锁

      1. 一级封锁协议:事务T在修改数据R之前必须对其加X锁,直到事物结束时释放,解决修改丢失问题,但不解决不可重复读和读脏数据问题。

      2. 二级封锁协议:在一级封锁协议的基础上,事务T在读取数据R前,必须对其加S锁,读完后即可释放,解决读脏数据问题,但解决不了不可重复读问题。

      3. 三级封锁协议:在一级封锁协议的基础上,事务T在读取数据R前,必须对其加S锁,直到事务结束方可释放,解决不可重复读问题。

SAXS

2 数据库连接池


2.1 JDBC数据连接池的必要性

  • 在使用开发基于数据库的web程序时,传统的模式基本是按以下三步:
      1. 在主程序中建立数据库连接
      2. 进行sql操作
      3. 断开数据库连接
  • 这种开发模式,存在问题:
      1. 普通的JDBC数据库连接使用DriverManger获取,每次向数据库建立连接时都要将Connection加载到内存中,再验证用户名和密码正确性(需花费0.05s~1s不等的时间)。这种方式会消耗大量的资源跟时间。数据库的连接资源并没有得到很好的重复利用
      2. 每一次数据库连接,使用完后都得断开。因为如果不断开会出现java的内存泄露。
        • 何为jav的内存泄露:创建完的对象没有被正常回收。
      3. 这种开发不能控制被创建的连接对象数,系统资源会被毫无顾忌的分配出去。

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 连接池的代码实现

  • 这里演练最常用的德鲁伊连接池
  1. 加载配置文件,命名为:druid.properties
url:jdbc:mysql:///test
username=root
password=password
driverClassName=com.mysql.cj.jdbc.Driver
initialSize=10
maxActive=10
  1. 实现连接
  • 调用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包的步骤:
  1. 创建Directory文件lib;
  2. 进入alibaba的druid的下载地址:https://repo1.maven.org/maven2/com/alibaba/druid/。
  3. IDEA的Library添加配置。

  1. 出现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包已经导入,可以使用。
  1. **出现: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依赖。

posted @   gdxstart  阅读(39)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示