typora-copy-images-to: imgs

1. 1.JDBC

1.1. 1、JDBC实现对单表数据查询

JDBC通过statement执行sql后,会将查询的结果封装到ResultSet下;

1.1 ResultSet的原理

ResultSet类似之前学过的游标:

  1. ResultSet内部有一个指针,刚开始记录开始位置
  2. 调用next方法, ResultSet内部指针会移动到下一行数据
  3. 我们可以通过ResultSet得到一行数据 getXxx得到某列数据

1.2 ResultSet获取数据的API

其实ResultSet获取数据的API是有规律的get后面加数据类型。我们统称getXXX()

举例:

rs.getString("username"); //通过列名获取该列的值。
rs.getString(2); //通过username列所在的第二个位置获取该列的值(索引位从1开始)。

image-20201021104511426

1604460022090

1.3 使用JDBC查询数据库中的数据的步骤

  1. 注册驱动
  2. 获取连接
  3. 获取到Statement
  4. 使用Statement执行SQL
  5. ResultSet处理结果(ResultSet)
  6. 关闭资源

说明:我们使用JDBC操作数据库的步骤都是固定的,不同的地方是在编写SQL语句;

1.4 练习

练习:完成对user表中所有数据的查询

    @Test
    public void test7() throws ClassNotFoundException, SQLException {
        //1)注册驱动
        Class.forName("com.mysql.jdbc.Driver");
        //2)创建连接对象
        String url="jdbc:mysql:///day05_1";
        String user="root";
        String pwd="1234";
        Connection conn = DriverManager.getConnection(url, user, pwd);
        //3)创建发送sql的对象
        Statement stm = conn.createStatement();
        //4)发送sql语句,获取ResultSet结果集
        String selectSql="select * from user";
        ResultSet rs = stm.executeQuery(selectSql);
        //5)解析结果集
        while (rs.next()){
            int id=rs.getInt(1);
            String name=rs.getString("username");
            String password=rs.getString(3);
            System.out.println(id+":"+name+":"+password);
        }
        //6)释放资源,关闭连接
        rs.close();
        stm.close();
        conn.close();
    }

1604460664135

小结:

1.jdbc查询使用步骤?

1.注册驱动
  Calss.forName(驱动类全限定名称);
2.获取连接对象
 Connection  conn= DriverManager.getConnection(url,userName,password);
3.获取发送sql的对象Statement  
  Statement stm= conn.createStatement();
4.组装查询sql并发送
  ResultSet rs= stm.executeQuery(sql);
5.解析结果集
  while(rs.next()){
    rs.getXX(2中获取值得方式)
  }
6.释放资源,关闭连接
  rs.close();
  stm.close();
  conn.close();

2.ResultSet如何获取数据?

-- 2种获取数据的方式
1.rs.getXX(字段索引位,从1开始);
2.rs.getXX(字段名称);

1.2. 2、编写JDBC工具类

1.2.1. 2.1 讲解

​ 在上面的学习过程中,我们发现我们有很多重复的操作。那么这样一方面来说对我们开发带来了不便,更多的时候是当我遇到如下的问题:

数据库的用户名发生了变化,这时候我们发现,我们需要修改每处获取连接的用户名参数。这样是对于我们后期的维护是非常繁琐的。所以我们需要对jdbc操作数据库的步骤的一些常用的方法抽出来,放到一个外部的工具类中。

1.2.2. 2.2 获得连接的初步抽取

需求:首先创建一个JDBCUtils 工具类:然后将jdbc中获取连接的方法截止抽取到工具类方法中;

public class JDBCUtils {

    private static  Connection conn ;

    /**
     * 获取连接对象
     * @return
     */
    public static Connection getConnection(){
        //1)注册驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
        //2)创建连接对象
        String url="jdbc:mysql:///day05_1";
        String user="root";
        String pwd="1234";
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url, user, pwd);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }

    /**
     * 释放资源,关闭连接
     * @param rs
     * @param stm
     * @param conn
     */
    public static void close(ResultSet rs, Statement stm,Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                System.out.println("rs:"+e.getMessage());
            }
        }
        if(stm!=null){
            try {
                stm.close();
            } catch (SQLException e) {
                System.out.println("stm:"+e.getMessage());
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                System.out.println("conn:"+e.getMessage());
            }
        }
    }
}

1.2.3. 2.3 获得连接第二次优化

上述存在的问题:

1.数据库驱动的注册应只注册一次即可;

2.配置参数硬编码置入代码,不方便维护;

解决思路:

​ 1.在工具类中创建静态代码块,做到驱动值注册一次;

​ 2.将方法中的参数都以变量的形式抽离出来,这样方便这些参数的统一管理。

public class JDBCUtils2 {


    private static  Connection conn ;
    private static String url;
    private static String userName;
    private  static String password;

    static {
        //1)注册驱动
        try {
            Class.forName("com.mysql.jdbc.Driver");
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }
        url="jdbc:mysql:///day05_1";
        userName="root";
        password="1234";
    }

    /**
     * 获取连接对象
     * @return
     */
    public static Connection getConnection(){
        //2)创建连接对象
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url, userName, password);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }

    /**
     * 释放资源,关闭连接
     * @param rs
     * @param stm
     * @param conn
     */
    public static void close(ResultSet rs, Statement stm,Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                System.out.println("rs:"+e.getMessage());
            }
        }
        if(stm!=null){
            try {
                stm.close();
            } catch (SQLException e) {
                System.out.println("stm:"+e.getMessage());
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                System.out.println("conn:"+e.getMessage());
            }
        }
    }
}

1.2.4. 2.4 配置文件方式定义变量

上述封装存在的问题:

驱动名称,用户名,密码是硬编码的方式植入到代码中的,后期需要修改时,需要重新编译java文件;

如何避免?

​ 可以将配置文件存储在外部文件中,通过java io读取配置文件,这样既可以几种配置管理,又避免了java类因为参数的修改的问题,反复进行编译的问题;

思想:

动静分离,将java中的代码与经常变动的变量进行分离,变量存放到一个静态文件文件中,这样变量修改后,不需要重新编译java文件;

练习:在src资源路径下创建jdbc.properties配置文件,并通过Properties类加载配置,实现参数加载

​ 说明:1.添加配置文件 2.类加载器加载配置文件 3.Properties对象动态获取配置信息 4.初始化连接对象;

1.创建jdbc.properties文件

jdbc.url=jdbc:mysql:///day05_1
jdbc.userName=root
jdbc.password=1234
jdbc.driverClass=com.mysql.jdbc.Driver

2.封装工具类

public class JDBCUtils3 {

    private static  Connection conn ;
    private static String url;
    private static String userName;
    private  static String password;
    private  static String driverClass;

    static {
       // new FileInputStream("src/jdbc.properties");
        InputStream in = JDBCUtils3.class.getClassLoader().getResourceAsStream("jdbc.properties");
        Properties properties = new Properties();
        try {
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
        url=properties.getProperty("jdbc.url");
        userName=properties.getProperty("jdbc.userName");
        password=properties.getProperty("jdbc.password");
        driverClass=properties.getProperty("jdbc.driverClass");
        //1)注册驱动
        try {
            Class.forName(driverClass);
        } catch (ClassNotFoundException e) {
            System.out.println(e.getMessage());
        }

    }

    /**
     * 获取连接对象
     * @return
     */
    public static Connection getConnection(){
        //2)创建连接对象
        Connection conn = null;
        try {
            conn = DriverManager.getConnection(url, userName, password);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return conn;
    }
}

1.2.5. 2.5 关闭资源

关闭资源的顺序:1.ResultSet结果集 2.Statement 3.Connection

    /**
     * 释放资源,关闭连接
     * @param rs
     * @param stm
     * @param conn
     */
    public static void close(ResultSet rs, Statement stm,Connection conn){
        if(rs!=null){
            try {
                rs.close();
            } catch (SQLException e) {
                System.out.println("rs:"+e.getMessage());
            }
        }
        if(stm!=null){
            try {
                stm.close();
            } catch (SQLException e) {
                System.out.println("stm:"+e.getMessage());
            }
        }
        if(conn!=null){
            try {
                conn.close();
            } catch (SQLException e) {
                System.out.println("conn:"+e.getMessage());
            }
        }
    }

1.2.6. 2.6 工具类优化后代用查询方法

    @Test
    public void test8() throws SQLException {
        //1)获取连接对象
        Connection conn = JDBCUtils3.getConnection();
        //3)创建发送sql的对象
        Statement stm = conn.createStatement();
        //4)发送sql语句,获取ResultSet结果集
        String selectSql="select * from user";
        ResultSet rs = stm.executeQuery(selectSql);
        //5)解析结果集
        while (rs.next()){
            int id=rs.getInt(1);
            String name=rs.getString("username");
            String password=rs.getString(3);
            System.out.println(id+":"+name+":"+password);
        }
        //释放资源
        JDBCUtils3.close(rs,stm,conn);
    }

1.3. 3、JDBC事务

之前我们是使用MySQL的命令来操作事务。接下来我们使用JDBC来操作银行转账的事务。

1.数据准备:

# 创建账号表
create table account(
    id int primary key auto_increment,
    name varchar(20),
    money double
);
# 初始化数据
insert into account values (null,'a',1000);
insert into account values (null,'b',1000);

2.API介绍

Connection接口与事务相关的核心API:

void setAutoCommit(boolean autoCommit) throws SQLException;// true:自动提交 flase:手动提交
void commit() throws SQLException;//提交事务
void rollback() throws SQLException;//回滚事务

注意:

在jdbc事务操作中,事务的控制都是通过Connection对象完成的,当一个完整的业务操作前,我们首先使用conn.setAutoCommit(false)来设置事务手动提交。默认情况下是true的,表示自动提交事务,那么一条sql语句就是一个事务,默认提交事务。如果设置为false,那么表示开启事务,所有的sql语句就会都在一个事务中。

​ 当业务操作完成之后,如果整个操作没有问题,我们需要使用conn.commit()来提交事务。当然了,如果出现了异常,我们需要使用conn.rollback()撤销所有的操作,所以出现异常,需要进行事务的回滚。

3.使用步骤

  1. 注册驱动
  2. 获取连接
  3. 开启事务(设置事务手动提交)
  4. 获取到Statement
  5. 使用Statement执行SQL
  6. 提交或回滚事务
  7. 关闭资源

4.案例代码

需求:以a给b转账100业务为例;

public class TransferDemo {

    //需求:以a给b转账100业务为例;
    public static void main(String[] args) {
        Connection conn=null;
        Statement stm=null;
        try {
            //1)注册驱动
            Class.forName("com.mysql.jdbc.Driver");
            //2)获取连接对象
            String url="jdbc:mysql://localhost:3306/day05_1";
            String userName="root";
            String password="1234";
            conn = DriverManager.getConnection(url, userName, password);
            //3)设置事务手动提交
            conn.setAutoCommit(false);
            //4)获取发送sql的对象Statement
            stm = conn.createStatement();
            //5)组装sql并发送
            String fromMoney="update account set money=money-100 where name='a'";
            stm.executeUpdate(fromMoney);
            String toMoney="update account set money=money+100 where name='b'";
            stm.executeUpdate(toMoney);
            //6)事务提交
            conn.commit();
        }catch (Exception e){
            System.out.println(e.getMessage());
            //回滚事务
            try {
                conn.rollback();
            } catch (SQLException er) {
                System.out.println(er.getMessage());
            }
        }
        finally {
            // 释放资源
            if(stm!=null){
                try {
                    stm.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }
            if(conn!=null){
                try {
                    conn.close();
                } catch (SQLException throwables) {
                    throwables.printStackTrace();
                }
            }

        }

    }
}

小结:

JDBC中与事务相关的API有哪些?

//事务手动提交设置
conn.setAutoCommit(false);
//事务提交
conn.commit();
//事务回滚
conn.rollback();

1.4. 4、JDBC实现登录案例

需求:

1. 使用SQL根据用户的账号和密码去数据库查询数据
2. 如果查询到数据,说明登录成功
3. 如果查询不到数据,说明登录失败

数据准备:

create table user(
    id int primary key auto_increment,
    name varchar(30),
    password varchar(20)
);
insert into user values(null,'张三','123'),(null,'李四','456');

实现:

public class LoginDemo {

    public static void main(String[] args) throws SQLException {
        //1)获取连接对象
        Connection conn = JDBCUtils3.getConnection();
        //2)获取发送sql的对象
        Statement stm = conn.createStatement();
        //3)发送sql语句,解析结果集
        //select * from user where username='zhangsan' and password='123';
        String user="zhangsan";
        String password="123";
        String loginSql="select * from user where username='"+user+"' and password='"+password+"'";
        ResultSet rs = stm.executeQuery(loginSql);
        if(rs.next()){
            System.out.println("登录成功!");
        }else{
            System.out.println("您输入的用户名或者密码错误!");
        }
        //4)释放资源,关闭连接
        JDBCUtils3.close(rs,stm,conn);
    }
}

1.5. 5、PreparedSatement预编译对象

5.1 SQL注入问题

sql注入:

​ 由于没有对用户输入进行充分检查,而SQL又是拼接而成,在用户输入参数时,在参数中添加一些SQL 关键字,达到改变SQL运行结果的目的,也可以完成恶意攻击。

简单来说就是:用户在页面提交数据的时候人为的添加一些特殊字符,使得sql语句的结构发生了变化,最终可以在没有用户名或者密码的情况下进行登录。

ext:
https://www.cnblogs.com/geaozhang/p/9891338.html

5.2 sql注入原因演示_模拟登陆:

需求: 登录功能演示SQL注入;

    public static void main(String[] args) throws SQLException {
        //1)获取连接对象
        Connection conn = JDBCUtils3.getConnection();
        //2)获取发送sql的对象
        Statement stm = conn.createStatement();
        //3)发送sql语句,解析结果集
        //select * from user where username='zhangsan' and password='123';
        //说明:知道用户名,但是不知道密码,还想登录成功?
        //select * from user where username='zhangsan' -- ' and password='123'
        //String user="zhangsan' -- ";
        //不知道用户名和密码正常登录
        //select * from user where username='xxx' or 1=1 --  ' and password='123';
        String user="xxx' or 1=1 --  ";
        String password="123";
        String loginSql="select * from user where username='"+user+"' and password='"+password+"'";
        System.out.println(loginSql);
        ResultSet rs = stm.executeQuery(loginSql);
        if(rs.next()){
            System.out.println("登录成功!");
        }else{
            System.out.println("您输入的用户名或者密码错误!");
        }
        //4)释放资源,关闭连接
        JDBCUtils3.close(rs,stm,conn);
    }

1.6. 6、PreparedStatement解决SQL注入方案

image-20201021144925862

1.获取PreparedStatement对象

PreparedStatement是Statement的子接口,可以防止sql注入问题。可以通过Connection接口中的prepareStatement(sql)方法获得PreparedStatement的对象。

方法如下所示:

//创建一个 PreparedStatement 对象来将参数化的 SQL 语句发送到数据库 
PreparedStatement    prepareStatement(String sql);

注意:sql提前创建好的。sql语句中需要参数。使用进行占位。

举例:

select *from user where username='zhangsan' and password = '123456';

使用?进行占位

select *from user where username=? and password = ?;

String sql=”select *from user where username=? and password = ?”;

步骤一:PreparedStatement  pstmt =  conn.prepareStatement(sql); -----需要你事先传递sql模板。如果sql需要参数,使用?进行占位。

步骤二:设置参数(执行sql之前):pstmt.setXXX(int index, 要放入的值) -----根据不同类型的数据进行方法的选择。第一个参数index表示的是?出现的位置。**从1开始计数,有几个问号,就需要传递几个参数**。
方法的参数说明:

第一个参数:int index ;表示的是问号出现的位置。 问号是从1开始计数
第二个参数:给问号的位置传入的值。
步骤三、执行,**不需要在传递sql了**。
   pstmt.executeQuery();---执行select
   pstmt.executeUpdate();---执行insert,delete,update

实现:预编译实现用户登录;

public class prepareLoginDemo {
    public static void main(String[] args) throws SQLException {
        //1)获取连接对象
        Connection conn = JDBCUtils3.getConnection();
        //2)获取预编译对象
        String loginSql="select * from user where username=? and password=?";
        //说明:获取预编译对象时,sql模板已经发送给mysql服务端进行了预编译;
        PreparedStatement pstm = conn.prepareStatement(loginSql);
        //3)给?占位符赋值 根据占位符的位置从1开始,一次类推
        // setXX(参数索引位,值)---》 xx表示字段是什么类型,就set什么类型
        pstm.setString(1,"zhang' or 1=1 --  ");
        pstm.setString(2,"123");
        //4)发送参数,返回结果
        ResultSet rs = pstm.executeQuery();
        if(rs.next()){
            System.out.println("登录成功!");
        }else{
            System.out.println("用户名或者密码错误!");
        }
        //4)释放资源,关闭连接
        JDBCUtils3.close(rs,pstm,conn);
    }
}

小结:

1)Statement 
  存在sql注入的风险;
2)PreparedStatement 预编译对象  
  防止sql注入;
  使用方式:
  PreparedStatement pstm= conn.prepareStatement(sql模板);
  pstm.setXX(参数索引位置,值);
  ResultSet rs=pstm.executeQuery();
  int count=pstm.executeUpdate();

1.7. 7、PreparedStatement的应用(掌握)

练习: 预编译实现插入用户名 liuyan ,密码123.

    @Test
    public void test1() throws SQLException {
        //1)获取连接对象
        Connection conn = JDBCUtils3.getConnection();
        //2)获取预编译对象(需要绑定sql)
        String insertSql="insert into user values(null,?,?)";
        PreparedStatement pstm = conn.prepareStatement(insertSql);
        //3)设置发送
        pstm.setString(1,"柳岩");
        pstm.setString(2,"12345");
        //4)发送sql,执行sql
        int count = pstm.executeUpdate();
        System.out.println(count);
        //5)关闭资源,释放连接
        JDBCUtils3.close(null,pstm,conn);

    }

1604475735855

1.8. 8.执行DQL查询结果封装成集合的操作

在实际开发中,中小型项目都遵循三层架构开发,jdbc从数据库持久层获取数据后,都会封装成一个对象返还给逻辑层应用;

练习:使用jdbc查询用户信息,并封装到User对象下

说明:目前对象属性较少,不算太过复杂,如果一个对象属性非常多,使用上述方式就非常麻烦,后续我们会使用mybatis框架自动封装;

    @Test
    public void test8() throws SQLException {
        //1)获取连接对象
        Connection conn = JDBCUtils3.getConnection();
        //3)创建发送sql的对象
        Statement stm = conn.createStatement();
        //4)发送sql语句,获取ResultSet结果集
        String selectSql="select * from user";
        ResultSet rs = stm.executeQuery(selectSql);
        //5)解析结果集
        ArrayList<User> users = new ArrayList<>();
        while (rs.next()){
            int id=rs.getInt(1);
            String name=rs.getString("username");
            String password=rs.getString(3);
            User user = new User();
            user.setId(id);
            user.setPassword(password);
            user.setUserName(name);
            users.add(user);
        }
        System.out.println(users);
        //释放资源
        JDBCUtils3.close(rs,stm,conn);
    }

1.9. 9、使用JDBC调用存储过程(了解)

JDBC调用存储过程API:

1)获取连接Connection的对象

2)使用连接Connection的对象调用连接Connection的对象接口中的方法获取CallableStatement接口对象

 CallableStatement prepareCall(String sql) 创建一个 CallableStatement 对象来调用数据库存储过程。 
     参数:
        sql - 可以包含一个或多个 '?' 参数占位符的 SQL 语句。通常此语句是使用 JDBC 调用转义语法指定的。
         说明:参数sql中书写的是存储过程语句,语法是固定的写法:
             1)带参数的存储过程写法:
             String sql="{call 存储过程名(?,?, ...)}";
                IN 参数值是使用继承自 PreparedStatement 的 set 方法设置的.
                    从左向右第一个占位符编号是 1,第二个占位符编号是2,依次递增。
              void setString(int parameterIndex, String x)将指定参数设置为给定Java String 值。 
              void setInt(int parameterIndex, int x)  将指定参数设置为给定 Java int 值。 
                 参数:parameterIndex表示第几个占位符 x 表示给占位符赋的实际值
             在执行存储过程之前,必须注册所有 OUT 参数的类型。注册参数类型使用registerOutParameter                 方法来完成的。
                void registerOutParameter(int parameterIndex, int sqlType) 
                      按顺序位置 parameterIndex 将 OUT 参数注册为 JDBC 类型 sqlType。      
                        参数:
                        parameterIndex:第一个参数是 1,第二个参数是 2,依此类推
                        sqlType:java.sql.Types 定义的 JDBC 类型代码。例如:Types.INTEGER
              语句执行完后,CallableStatement的getXXX方法将取回参数值:
                 int getInt(int parameterIndex) 以 Java 编程语言中 int 值的形式获取指定的 JDBC                     INTEGER 参数的值。 
                     参数:表示获取的值属于第几个占位符,上述占位符中属于第四个,所以这里写4

            2) 不带参数的储存过程的语法:
            String sql="{call 存储过程名}";

3)使用CallableStatement接口的父接口PreparedStatement的方法执行存储过程的sql语句:

 boolean execute() 在此 PreparedStatement 对象中执行 SQL 语句,该语句可以是任何种类的 SQL 语句。

4)使用CallableStatement的getXXX方法获取回参数值:

int getInt(int parameterIndex) 以 Java 编程语言中 int 值的形式获取指定的 JDBC的INTEGER 参数的值。 
    参数:表示获取的值属于第几个占位符,上述占位符中属于第四个,所以这里写4

5)输出存储过程的结果

6)释放资源

需求:使用jdbc技术调用之前编写的转账的存储过程

数据准备:

/*
    参数:
        fromSub 从哪位用户转账
        toSub 转给的用户
        m 转账金额
        flag 标记,1表示转账成功  0表示转账失败
*/
-- 存储过程sql
DELIMITER $
CREATE  procedure transfer(in fromSub VARCHAR(20),in toSub VARCHAR(20),in m FLOAT,OUT flag INT)
BEGIN
        -- i1变量存储转出账的ROW_COUNT()结果
        declare i1 INT DEFAULT 0;
        -- i2变量存储转入账的ROW_COUNT()结果
        declare i2 INT DEFAULT 0;
        START TRANSACTION;
        -- 转出账 扣钱
        UPDATE account SET money = money - m WHERE name=fromSub;
        -- 将ROW_COUNT()即上次修改影响的行数1保存到变量i1中
        SELECT ROW_COUNT() INTO i1;
         -- 转入账 
        UPDATE account SET money = money + m WHERE name=toSub;
        -- 将ROW_COUNT()即上次修改影响的行数1保存到变量i2中
        SELECT ROW_COUNT() INTO i2;
        IF i1>0 AND i2>0 THEN
                -- 提交事务
                COMMIT;
                SET flag = 1;
        ELSE
                -- 回滚事务
                ROLLBACK;
                SET flag = 0;
        END IF;
        SELECT CONCAT(i1,'---',i2);        
END $

-- mysql调用存储过程
call transfer('tom','rose',100,@flag)$

练习:JDBC代码调用存储过程实现转账业务;

    @Test
    public void test2() throws SQLException {
        //1)获取连接对象
        Connection conn = JDBCUtils3.getConnection();
        //2)获取CallableStatement
        String callSql="{call transfer(?,?,?,?)}";
        CallableStatement cstm = conn.prepareCall(callSql);
        //3)入参设置
        cstm.setString(1,"a");
        cstm.setString(2,"b");
        cstm.setDouble(3,100d);
        //4)注册出参
        cstm.registerOutParameter(4, Types.INTEGER);
        //5)执行存储过程
        cstm.execute();
        //6)获取出参
        int flag = cstm.getInt(4);
        System.out.println(flag==1?"转账成功!":"转账失败!");
        //7)释放资源,关闭连接
        JDBCUtils3.close(null,cstm,conn);

    }

1.10. 10、三层开发业务的案例分析(理解)

image-20201020201621907

1、开发中,常使用分层思想

​ 1) 不同的层次结构分配不同的解决过程,各个层次间组成严密的封闭系统

​ 2)不同层级结构彼此平等

​ 3)分层的目的是:

​ a:解耦,就是降低代码之间的依赖关系。

​ b:可维护性,哪一层出现问题,直接维护哪一层。

​ c:可扩展性,哪一层需要添加代码,直接添加即可。

​ d:可重用性,一个方法可以被其它层重复调用。

2、不同层次,使用不同的包表示

​ 1)com.itheima.web web层 公司域名倒写。和前端页面直接交互。

​ 2)com.itheima.service service层。也叫做业务层。专门用来处理业务的,比如事务。

​ 3)com.itheima.dao dao层。数据处理层。操作数据库的代码都书写在这一层。直接和数据库交互。

​ 4)com.itheima.domain/entity/bean/pojo javabean 存放实体类。临时存放数据

​ 5)com.itheima.utils 存放工具类。

小结:

3层架构好处:
1)各个层,比如web,service,dao层解耦;
2)提高了代码的复用性;
3)提高了可维护性;

2. 2.连接池

image-20201021163106023

image-20201021163149445

2.1. 1、连接池介绍

1、操作数据库都需要创建连接,操作完成还需要关闭连接

2、创建连接和关闭连接需要可能比执行sql需要的时间都长

3、一个网站需要高频繁的访问数据库,如果短时间频繁的访问数据库服务器,就容易造成服务器的宕机,即死机。

连接池解决现状问题的原理

注意:

分析:当前的jdbc程序每次访问数据库都需要创建一个新的连接,访问完毕之后,还需要释放资源。那么在这样的一个过程中,连接的创建和销毁所消耗的资源是远远大于我们发送sql并执行的时间的。基于这样的情况,我们发现我们的jdbc程序将大量的资源浪费在了连接的创建和销毁上。

举例:就像在上海坐地铁,就一站2分钟的路程,往往在买地铁票的过程需要等待至少10分钟以上的时间。这样是不合理的。所以我们 需要对这样的结构进行优化。

思考上面的结构,大部分的时间浪费在了创建和销毁上。那么我们能不能实现将这些连接回收和利用呢?这样我们就不需要不停的创建和销毁了。只需要创建一次,放在指定的地方。当我们使用的时候,直接从里面拿就行了。用完放回原来的地方。不去销毁,当我再次使用的时候,去拿就行了。而这样的解决方案就是我们需要的。

优化后的结构如下:

说明:首先创建一定数量的连接,然后放到指定的地方。当我们需要获取连接的时候,直接从指定的地方获取。用完了,我们再将连接放回去。这样就能将我们连接的回收利用。并且不用花费大量时间在创建和销毁连接上。

连接池好处

连接池中保存了一些数据库连接,这些连接是可以重复使用的。节省数据库的资源消耗

常用连接池的介绍

javax.sql.DataSource表示数据库连接池,DataSource本身只是Sun公司提供的一个接口,没有具体的实现,它的实现由连接池的数据库厂商去实现。我们只需要学习这个工具如何使用即可。

该接口如下:

public interface DataSource {
    Connection getConnection();
}

常用的连接池实现组件有以下这些:

  1. 阿里巴巴-德鲁伊Druid连接池:Druid是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池、插件框架和SQL解析器组成。该项目主要是为了扩展JDBC的一些限制,可以让程序员实现一些特殊的需求。
  2. C3P0是一个开源的JDBC连接池,支持JDBC3规范和JDBC2的标准扩展。目前使用它的开源项目有Hibernate,Spring等。C3P0有自动回收空闲连接功能。
  3. DBCP(DataBase Connection Pool)数据库连接池,是Apache上的一个Java连接池项目。dbcp没有自动回收空闲连接的功能。

小结:

1.连接池的好处?

1)提高了连接对象的复用性;
2)避免了数据库连接对象反复的创建与销毁带来的性能开销;
3)提高了数据库的性能;
4)从客户端角度看避免了oom(out of memory error);

2.连接池的原理?

1)初始化一定数量的数据库连接对象;
2)客户端不需要自己创建连接对象,直接从连接池中获取连接对象,直接使用即可;
3)客户端使用完毕后,归还连接对象到连接池;

2.2. 2、C3P0连接池

准备数据:

create table emp(
    id int primary key auto_increment,
    name varchar(50),
    city varchar(50)
);

insert into emp values(null, '刘备', '北京');
insert into emp values(null, '关羽', '上海');
insert into emp values(null, '张飞', '广州');
select * from emp;

C3P0连接池简介

C3P0 是一个开源的JDBC连接池,目前spring 和 hibernate框架对C3P0是支持的。

使用c3p0数据库连接池之前,首先需要在资料中找到如下的jar包,加载到项目中。

C3P0常用的配置参数解释

参数 说明
driverClass 数据库驱动类。例如:com.mysql.jdbc.Driver
jdbcUrl 连接数据库的url地址。例如:jdbc:mysql://localhost:3306/day05_db
user 连接数据库的用户名。例如:root
password 连接数据库的密码。例如:1234
initialPoolSize 刚创建好连接池的时候连接数量
maxPoolSize 连接池中最多可以放多少个连接

API介绍

com.mchange.v2.c3p0.ComboPooledDataSource类表示C3P0的连接池对象,常用2种创建连接池的方式:

1.无参构造,使用默认配置

2.有参构造,使用命名配置

  1. public ComboPooledDataSource()
    无参构造使用默认配置(使用xml中default-config标签中对应的参数)
    
  2. public ComboPooledDataSource(String configName)
    有参构造使用命名配置(configName:xml中配置的名称,使用xml中named-config标签中对应的参数)
    
  3. public Connection getConnection() throws SQLException
    从连接池中取出一个连接
    

使用步骤

1.导入jar包c3p0-0.9.1.2.jar

1604479969612

2.编写c3p0-config.xml配置文件,配置对应参数

<?xml version="1.0" encoding="utf-8" ?>
<!--配置的跟标签-->
<c3p0-config>
    <!--默认数据源配置-->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///day05_1</property>
        <property name="user">root</property>
        <property name="password">1234</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
    </default-config>

    <!-- This app is massive! -->
    <named-config name="xx">
    </named-config>
</c3p0-config>

3.将配置文件放在src目录下

1604480612382

4.创建连接池对象ComboPooledDataSource使用默认配置或命名配置

        //创建数据源(连接池对象)对象,自动加载src根路径下c3p0-config.xml
        ComboPooledDataSource dataSource = new ComboPooledDataSource();

5.从连接池中获取连接对象

        //获取连接对象
        Connection conn = dataSource.getConnection();

6.使用连接对象操作数据库

        //3)创建发送sql的对象
        Statement stm = conn.createStatement();
        //4)发送sql语句,获取ResultSet结果集
        String selectSql="select * from user";
        ResultSet rs = stm.executeQuery(selectSql);
        //5)解析结果集
        ArrayList<User> users = new ArrayList<>();
        while (rs.next()){
            int id=rs.getInt(1);
            String name=rs.getString("username");
            String password=rs.getString(3);
            User user = new User();
            user.setId(id);
            user.setPassword(password);
            user.setUserName(name);
            users.add(user);
        }
        System.out.println(users);

6.关闭资源,归还连接

        //6)close 归还连接对象到连接池
        rs.close();
        stm.close();
        conn.close();

注意事项

C3P0配置文件名称必须为c3p0-config.xml C3P0命名配置可以有多个

1)案例代码

需求:查询表中所有数据并输出到控制台。

package com.test.pool;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import com.test.pojo.User;
import org.junit.Test;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;

public class C3P0Demo {

    @Test
    public void test1() throws SQLException {
        //创建数据源(连接池对象)对象,自动加载src根路径下c3p0-config.xml
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        //获取连接对象
        Connection conn = dataSource.getConnection();
        //3)创建发送sql的对象
        Statement stm = conn.createStatement();
        //4)发送sql语句,获取ResultSet结果集
        String selectSql="select * from user";
        ResultSet rs = stm.executeQuery(selectSql);
        //5)解析结果集
        ArrayList<User> users = new ArrayList<>();
        while (rs.next()){
            int id=rs.getInt(1);
            String name=rs.getString("username");
            String password=rs.getString(3);
            User user = new User();
            user.setId(id);
            user.setPassword(password);
            user.setUserName(name);
            users.add(user);
        }
        System.out.println(users);
        //6)close 归还连接对象到连接池
        rs.close();
        stm.close();
        conn.close();
    }
}

2)使用命名配置操作指定的数据库

需求:读取数据库下面的user表中的信息。

1)修改c3p0-config.xml配置文件中的配置信息;
2)连接池对象指定数据库环境
   ComboPooledDataSource dataSource =new ComboPooledDataSource("dev")

1)条件其他数据源配置

<?xml version="1.0" encoding="utf-8" ?>
<!--配置的跟标签-->
<c3p0-config>
    <!--默认数据源配置-->
    <default-config>
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///day05_1</property>
        <property name="user">root</property>
        <property name="password">1234</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
    </default-config>

    <!-- This app is massive! -->
    <named-config name="dev">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///day05_2</property>
        <property name="user">root</property>
        <property name="password">1234</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
    </named-config>
    <!-- This app is massive! -->
    <named-config name="test">
        <property name="driverClass">com.mysql.jdbc.Driver</property>
        <property name="jdbcUrl">jdbc:mysql:///day05_2</property>
        <property name="user">root</property>
        <property name="password">1234</property>
        <property name="initialPoolSize">10</property>
        <property name="maxIdleTime">30</property>
        <property name="maxPoolSize">100</property>
        <property name="minPoolSize">10</property>
    </named-config>
</c3p0-config>

2)在数据源构造器中指定数据库开发环境

    @Test
    public void test2() throws SQLException {
        //创建数据源(连接池对象)对象,自动加载src根路径下c3p0-config.xml
        //ComboPooledDataSource dataSource = new ComboPooledDataSource();
        //指定dev作为数据库开发环境
        ComboPooledDataSource dataSource = new ComboPooledDataSource("dev");
        //获取连接对象
        Connection conn = dataSource.getConnection();
        //3)创建发送sql的对象
        Statement stm = conn.createStatement();
        //4)发送sql语句,获取ResultSet结果集
        String selectSql="select * from user";
        ResultSet rs = stm.executeQuery(selectSql);
        //5)解析结果集
        ArrayList<User> users = new ArrayList<>();
        while (rs.next()){
            int id=rs.getInt(1);
            String name=rs.getString("username");
            String password=rs.getString(3);
            User user = new User();
            user.setId(id);
            user.setPassword(password);
            user.setUserName(name);
            users.add(user);
        }
        System.out.println(users);
        //6)close 归还连接对象到连接池
        rs.close();
        stm.close();
        conn.close();
    }

2.3. 3、Druid连接池

DRUID简介

​ Druid是阿里巴巴开发的号称为监控而生的数据库连接池(可以监控访问数据库的性能),Druid是目前最好的数据库连接池。在功能、性能、扩展性方面,都超过其他数据库连接池。Druid已经在阿里巴巴部署了超过600个应用,经过一年多生产环境大规模部署的严苛考验。如:一年一度的双十一活动,每年春运的抢火车票。

Druid的下载地址:<https://github.com/alibaba/druid>  
Druid连接池使用的jar包:druid-1.0.9.jar

Druid常用的配置参数

url 数据库连接字符串jdbc:mysql://localhost:3306/数据库名
username 数据库的用户名
password 数据库的密码
driverClassName 驱动类名。根据url自动识别,这一项可配可不配,如果不配置druid会根据url自动识别数据库的类型,然后选择相应的数据库驱动名
initialSize 初始化时建立的物理连接的个数。初始化发生在显式调用init方法,或者第一次获取连接对象时
maxActive 连接池中最大连接数
maxWait 获取连接时最长等待时间,单位是毫秒。

Druid连接池基本使用

API介绍

核心类:DruidDataSourceFactory

获取数据源的方法:使用com.alibaba.druid.pool.DruidDataSourceFactory类中的静态方法:

public static DataSource createDataSource(Properties properties)
创建一个连接池,连接池的参数使用properties中的数据

配置信息在properties属性对象中。

我们可以看到Druid连接池在创建的时候需要一个Properties对象来设置参数,所以我们使用properties文件来保存对应的参数。

Druid连接池的配置文件名称随便,放到src目录或者项目根目录下面加载 druid.properties文件内容:

# 数据库连接参数
url=jdbc:mysql://localhost:3306/day05_db
username=root
password=123
driverClassName=com.mysql.jdbc.Driver

使用步骤

1) 导入核心包druid-1.0.9.jar

1604481946087

2) 在项目下创建一个properties文件,文件名随意,设置对应参数

# 数据库连接参数
url=jdbc:mysql://localhost:3306/day05_1
username=root
password=1234
driverClassName=com.mysql.jdbc.Driver
initialSize=5

3) 加载properties文件的内容到Properties对象中

        //加载配置信息
        InputStream in = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
        //创建Properties配置对象
        Properties properties = new Properties();
        properties.load(in);

4) 创建DRUID连接池,使用配置文件中的参数

 //使用DruidDataSourceFactory工厂类构建连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);

5) 从DRUID连接池中取出连接

        //获取连接对象
        Connection conn = dataSource.getConnection();

6) 执行SQL语句

7) 关闭资源,归还连接

//6.归还资源
conn.close();

案例代码:

步骤1:属性文件:在项目下新建一个druid配置文件,命名为:druid.properties

url=jdbc:mysql:///day05_1
username=root
password=1234
driverClassName=com.mysql.jdbc.Driver
initialSize=5

步骤2:java代码:

public class DruidDemo {

    public static void main(String[] args) throws Exception {
        //加载配置信息
        InputStream in = DruidDemo.class.getClassLoader().getResourceAsStream("druid.properties");
        //创建Properties配置对象
        Properties properties = new Properties();
        properties.load(in);
        //获取连接池对象
        DataSource dataSource = DruidDataSourceFactory.createDataSource(properties);
        //获取连接对象
        Connection conn = dataSource.getConnection();
        //3)创建发送sql的对象
        Statement stm = conn.createStatement();
        //4)发送sql语句,获取ResultSet结果集
        String selectSql="select * from user";
        ResultSet rs = stm.executeQuery(selectSql);
        //5)解析结果集
        ArrayList<User> users = new ArrayList<>();
        while (rs.next()){
            int id=rs.getInt(1);
            String name=rs.getString("username");
            String password=rs.getString(3);
            User user = new User();
            user.setId(id);
            user.setPassword(password);
            user.setUserName(name);
            users.add(user);
         }
        System.out.println(users);
        //6)close 归还连接对象到连接池
        rs.close();
        stm.close();

        //归还资源
        conn.close();
    }
}

总结:

1)jdbc开发流程:
  1.注册驱动
    Class.forName(驱动类的全限定名称);
  2.获取连接对象
    conn=DriverManager.getConnection(url,user,pwd);
  3.获取发送sql的对象
    pstm= conn.prepareStatement(sql模板);//防止sql注入
    stm=conn.createStatement();//会有sql注入的问题
    CallableStatement cstm=conn.prepareCall("{call 存储过程名称(?,?,...)}");
  4.发送参数或者sql
    1)发送sql: executeQuery(sql);// select
               executeUpdate(sql);// insert delete update
    2)发送参数
              //设置参数
              比如:pstm.setXX(参数索引位,值);
              executeQuery();
              executeUpdate();
   5.结果相应
         1)返回受影响的行数
         2)返回结果集ResultSet
                next();true/false
                getXX(字段索引位,从1开始);
                getXX(字段名称);
    6.释放资源,关闭连接
      rs.close();
      stm.close();
      conn.close();
2)jdbc之事务
  jdbc默认事务自动提交;
  conn.setAutoCommit(false);//设置事务手动提交
  conn.commit();
  conn.rollback();
3)连接池
  常用的参数:
    1)数据库的4大参数-- url driverclass user pwd
    2) 初始化大小 最大连接数 最小连接数 等待时间等

results matching ""

    No results matching ""