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提供了统一的功能访问数据库

image-20221008192027863

  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包没导好;

解决:

image-20221029222723265

image-20221029222916474

可能会出现:空指针异常,说明还没配置到Liberal下

image-20221030110124481

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里的实现类都是处理查询要返回的具体内容操作

  • image-20221101193950669
  • 使用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添加配置。

image-20221101164637670

image-20221101165219438image-20221101165358569

image-20221101164720857

  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依赖。

原文地址:http://www.cnblogs.com/container-simple/p/16901267.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性