JDBC学习

JDBC学习

一级目录

二级目录

三级目录

一、JDBC开发的六个步骤

* jdbc编程的六个步骤
*    0、准备工作:导入数据库驱动jar包
*       a、在工程文件中新建一个lib文件夹,把驱动jar复制到里面去
*       b、将jar包家在打牌classpath下,右击jar-> add as library
*    1、注册数据库驱动
*    2、获取数据库连接 Connection对象
*    3、获取数据库库操对象 Statement对象
*    4、通过数据库操作对象执行SQL语句
*    5、返回结果、处理结果
*    6、释放资源
  • driver: com.mysql.jdbc.Driver–>这时候连接桥
  • url: jdbc:mysql://localhost:3306/jdbc_learn -->其中jdbc:mysql是协议, locaohost表示主机号,也可以写成127.0.0.1, 3306是端口号, 后面接上你需要连接的数据库
public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1、注册数据库驱动
        //方式1:不推荐使用,源码Driver加载的时候也是建立了,,所以就多余了
    /*
        Driver driver = new Driver();
        DriverManager.registerDriver(driver);*/
        //方式2:
        Class.forName("com.mysql.jdbc.Driver"); //只是触发类加载,类加载静态代码块就会执行,那么数据库驱动就会被注册,看Driver的源码

        //2、获取数据库连接的Connection对象
        String url = "jdbc:mysql://localhost:3306/jdbc_learn";
        String username = "root";
        String password = "975806";
        Connection connection = DriverManager.getConnection(url, username, password);
        System.out.println(connection);

        //3、获取数据库操作对象 Statement
        Statement statement = connection.createStatement();
        //4、操作执行sql语句
        String sql = "insert into user1(u_name,u_password,u_phone,u_email) values (\"yangkuang\",\"123456\", \"13476613854\",\"2242975806@qq.com\")";

        int i = statement.executeUpdate(sql);
        //5、返回结果,处理结果
        System.out.println(i);
        //6、释放资源
        statement.close();
        connection.close();
    }

二、SQL注入问题(Statement与preparement)

使用Statement进行sql语句执行的时候,有可能会发生sql注入问题,什么是sql注入问题呢,看看下面的代码例子就知道了

1、Statement 的sql注入问题
public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String uName  = sc.nextLine();
        System.out.println("请输入用户密码:");
        String uPassword = sc.nextLine();
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/jdbc_learn";
        String username = "root";
        String password = "975806";
        Connection connection = DriverManager.getConnection(url, username, password);
        Statement statement = connection.createStatement();
        String sql = "select * from tb_user where username = '" + uName + "' and  password = '"+ uPassword +"' ";
        System.out.println(sql);
        ResultSet resultSet = statement.executeQuery(sql);
        if(resultSet.next()) {
            System.out.println("登录成功");
        } else {
            System.out.println("登录失败");
        }
        resultSet.close();
        statement.close();
        connection.close();
    }

在这里插入图片描述

在这里插入图片描述

上面是数据库的表格,还有执行查询的结果,可以发现,使用Statement的时候,13行代码运行的时候,输入的用户名被转义了,导致了1=1变成了查询语句的一部分,这样子,不管用户是否存在,都显示查询成功。 |

2、使用PrepareStatement解决sql注入问题
public static void main(String[] args) throws SQLException, ClassNotFoundException {
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入用户名:");
        String uName  = sc.nextLine();
        System.out.println("请输入用户密码:");
        String uPassword = sc.nextLine();
        Class.forName("com.mysql.jdbc.Driver");
        String url = "jdbc:mysql://localhost:3306/jdbc_learn";
        String username = "root";
        String password = "975806";
        Connection connection = DriverManager.getConnection(url, username, password);

    	/**
    	下面使用了prepareStatement,需要注意的是,使用过程中,我们直接事用?表示占位,表示接下来要传入sql语句的变量,
    	然后需要的就是设置占位符的值
    	*/
        String sql = "select * from tb_user where username = ? and  password = ? ";//?号表示占位符
        PreparedStatement preparedStatement = connection.prepareStatement(sql);
        preparedStatement.setString(1, uName);//设置占位符的值,1表示第一个占位符
        preparedStatement.setString(2,uPassword);//2表示第二个占位符, uPassword表示值

        System.out.println(preparedStatement);
        ResultSet resultSet = preparedStatement.executeQuery();
        if(resultSet.next()) {
            System.out.println("登录成功");
        } else {
            System.out.println("登录失败");
        }
        resultSet.close();
        preparedStatement.close();
        connection.close();
    }
观察下面的运行结果,我们发现运行时,sql把整个名字传到占位符里面去,也就是说,名字不会被分开了,1=1自然也就不成立了,所以,数据库中没有存在该用户,所以就会登陆失败!!!
在这里插入图片描述

三、ORM对象关系映射

关系映射就是建立一个属性和数据库表格一样的实体类,也就是说,这个实体类就是表格的映射,我们可以通过实体类将数据插入表格,也可以将表格中查询到的数据赋值给实体类的属性,再打印出来

四、JDBC工具类

我们知道,JDBC开发一共需要6个步骤,每开启一个事务都需要,代码量就会显得相对多,而且操作不方便,所以我们可以将这些步骤其中一些封装起来,比如,jdbc驱动器的注册、jdbc连接对象的获取、还有就是关闭=释放资源都可以封装

封装的时候,比如我们注册驱动器、还有获取连接对象,我们不能将数据源(driver、url、username、password)封装在工具类里面,因为我们操作的数据库或者表格那些的使用是会发生变化的,所以可以写到配置文件里面去

1、数据源写到dp.properties文件中
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_learn?useSSL=false
username=root
password=975806
//注意格式,空格不要,不要写错数据源
2、工具类的封装

JDBCUtils

public class JDBCUtils {
    /**
     * 1、注册驱动
     *      数据库注册只需要一次,静态代码块中
     * 2、获取数据库连接
     *      封装一个方法,获取Connection 对象
     * 3、关闭资源
     *      封装一个方法,关闭资源
     * 4、将数据库的连接信息放到配置文件(.properties)中去,解决硬编码问题(只能连接特定的数据库)
     */

    private static String DRIVER;
    private static String URL;
    private static String USERNAME;
    private static String PASSWORD;
    //注册驱动
    static  {

        try {
            //读取配置文件中的数据源信息
            Properties properties = new Properties();
            //通过类加载器架加载classpath下的配置文件
            InputStream in = JDBCUtils.class.getClassLoader().getResourceAsStream("dp.properties");
            properties.load(in);
            //通过Properties对象读取流中的信息
            DRIVER = properties.getProperty("driver");
            URL = properties.getProperty("url");
            USERNAME = properties.getProperty("username");
            PASSWORD = properties.getProperty("password");
//            Class.forName("com.jdbc.mysql.Driver");
            Class.forName(DRIVER);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /***
     * ThreadLocal<T> 类似于集合,保证在同一线程下,拿到的T是同一个
     *      tl.set();
     *      tl.get();
     *      tl.remove();
     *
     * @return
     */

    static ThreadLocal<Connection> tl = new ThreadLocal<>();
    //修改Connection的注册,保证同一线程下,不同事务拿到的T是同一个

    public static Connection getConnection() {
        try{
            //1、从tl中获取connection对象
            Connection connection = tl.get();
            //没有的话,创建一个Connection, 如果tl中有就直接获取
            if(connection == null) {
                connection = DriverManager.getConnection(URL, USERNAME, PASSWORD);
                tl.set(connection);
            }
            return connection;
        }catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }

    //关闭释放资源
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if(resultSet != null) resultSet.close();
            if(statement != null) statement.close();
            if(connection != null) {
                connection.close(); 
                //此时关闭connection,但是ThreadLocal中还有保存这个connection,但是关闭了,下次不能用
                tl.remove();//移除关闭的connection
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    /**
     * 事务的封装
     */
    //开启事务
    public static void start() {
        Connection connection  = getConnection();
        try {
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //提交事务
    public static void commit() {
        Connection connection = getConnection();
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            closeAll(connection, null, null);
        }
    }

    //回滚事务
    public static void rollback() {
        Connection connection = getConnection();
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            closeAll(connection, null, null);
        }
    }

}

五、三层架构

在这里插入图片描述

在这里插入图片描述

六、JDBC事务处理

connection.setAutoCommit(false) ;//开启手动事务提交
connection.commit(); //提交事务
connection.rollback(); //事务回滚
转账问题
数据库表格程序架构
在这里插入图片描述
在这里插入图片描述

代码

1、DAO层(实现数据库信息的查询,更新等)
Accout : 实体类
public class Account {
    private Integer Id;
    private String Name;
    private Double Money;
    private String Password;

    @Override
    public String toString() {
        return "Account{" +
                "Id=" + Id +
                ", Name='" + Name + '\'' +
                ", Money=" + Money +
                ", Password='" + Password + '\'' +
                '}';
    }

    public Account(Integer id, String name, Double money, String password) {
        Id = id;
        Name = name;
        Money = money;
        Password = password;
    }

    public Integer getId() {
        return Id;
    }

    public void setId(Integer id) {
        Id = id;
    }

    public String getName() {
        return Name;
    }

    public void setName(String name) {
        Name = name;
    }

    public Double getMoney() {
        return Money;
    }

    public void setMoney(Double money) {
        Money = money;
    }

    public String getPassword() {
        return Password;
    }

    public void setPassword(String password) {
        Password = password;
    }
}

AccountDAO : 实体类操作数据库的接口,定义了查询啊,更新数据库的方法
public interface AccountDAO {
    Account selectAccountById(int id);
    int updateMoneyById(int id, double money);
}

AccountDAOImpl: 数据库实现类
public class AccountDAOImpl implements AccountDAO{

    @Override
    public Account selectAccountById(int id) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet res = null;

        connection = JDBCUtils.getConnection();
        String sql = "select * from account where id = ?";
        try {
            ps = connection.prepareStatement(sql);
            ps.setInt(1, id);
            res = ps.executeQuery();
            while(res.next()) {
                String name = res.getString("name");
                String password = res.getString("password");
                Double money = res.getDouble("money");
                Account account = new Account(id, name, money, password);
                return account;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeAll(null, ps, res);
        }
        return null;
    }

    @Override
    public int updateMoneyById(int id, double money) {
        Connection connection = null;
        PreparedStatement ps = null;
        ResultSet res = null;
        connection = JDBCUtils.getConnection();
        String sql = "update account set money = money + ? where id = ?";
        try {
            ps = connection.prepareStatement(sql);
            ps.setDouble(1, money);
            ps.setInt(2, id);
            int i = ps.executeUpdate();
            return i;
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            JDBCUtils.closeAll(null, ps, res);//设置了统一connetion后,只能在所有事务完成后才关
        }

        return 0;
    }
}

2、Service(业务层,实现银行转账的操作)
AccountService : 定义了实现业务的接口
public interface AccountService {
    public String transferAccounts(int fromId, String password, int toId, double money);
}
AccountServiceImpl: 实现业务接口的方法
public class AccountServiceImpl3 implements AccountService{
    AccountDAO accountDAO = new AccountDAOImpl();
    @Override
    public String transferAccounts(int fromId, String password, int toId, double money) {
        try {
            //1、开启事务
            JDBCUtils.start();
            Account account = accountDAO.selectAccountById(fromId);
            if (account == null) {
                return "账户不存在";
            }
            if (!account.getPassword().equals(password)) return "密码不正确";
            //判断余额是否充足
            if (account.getMoney() < money) {
                return "余额不足";
            }
            //查询对方卡号
            Account account1 = accountDAO.selectAccountById(toId);
            if (account == null) return "对方账号不存在";
            //我方扣钱
            int i = accountDAO.updateMoneyById(fromId, -money);
            //对方加钱
//            System.out.println(10 / 0);
//            System.out.println(10/0);---->这一句是错误代码,回导致上面的扣钱了,发生错误,而下面的没有加钱,因为两次是属于不同事务,
//            我们可以通过修改保证两个connetion是同一个,这样子后手动开始提交事务,就可以保证两个都完成,就不会哦出现一开始的为翁提
            int i1 = accountDAO.updateMoneyById(toId, money);
            //2、提交事务
            JDBCUtils.commit();
            return "转账成功";

        } catch (Exception e) {
            e.printStackTrace();
            //3、事务回滚
            JDBCUtils.rollback();
        }
        return "转账失败";

    }
}

3、testAccountnService: 测试类
public class testAccoutnService {
    public static void main(String[] args) {
        AccountService accountService = new AccountServiceImpl3();
        String s = accountService.transferAccounts(1, "123", 2, 200);
        System.out.println(s);
        String s2 = accountService.transferAccounts(1, "123", 2, 200);
        System.out.println(s2);
    }
}

需要注意的一个点事,我们的转账和另外一个人的入账是2个事务,而且两个事务必须同时成功,或者同时失败,但是有时我们会在两个事务过程中会发生错误,导致转账出去了,但是下一个事务没有成功,也就是说,转出去的事务成功了,二入账的事务没有成功

对于上面的事务错误的问题,我们可以手动开启事务,在所有事务完成后,一并提交 ,这样子就可以解决了。

但是我们会发现,有时事务提交,或者回滚失败?,这是因为Connection不是同一个,两个事务的connection不一样,还是会发生上面叙述的问题:

​ 对于Connection不一致,我们可以有两种办法解决:

​ 1、将Service中使用的Connection传递到DAO层中使用

​ 2、使用ThreadLocal类似实现,保证同一线程下的connection是同一个,不同线程下的是不同的connection,不仅解决了 connnection不一致的问题,还解决了多线程并发的问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VUpgsZ7v-1646457145961)(C:\Users\戴子儒\Desktop\截图\ThreadLocal解决Connection不一致的问题.png)]

七、数据库连接池Druid

在一个程序中,比如说一时间内有很多用户同时访问连接,会导致出现很大的问题,这样子我们就会引入Druid连接池。Druid是一个高效的数据查询系统,主要解决的是对于大量的基于时序的数据进行聚合查询

druid: 先创建好多个Connection连接对象,如果有空闲的就直接使用,没有的话就等待,使用完成的Connection不会关闭,而是返回到连接池

druid连接池的使用步骤
1、导入jar包(新建更目录下的lib文件夹下)
druid-1.2.0.jar
hamcrest-core-1.3.jar
junit-4.12.jar
mysql-connector-java-8.0.22.jar

2、 编写数据源配置文件(.properties)
driverClass=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/jdbc_learn?useSSL=false
username=root
password=975806

#下面四个不写也没有事,因为会有默认值

#初始化连接数 (默认里面已经有10个connection)
initialSize=10
#最大连接数 (当初始化的10个连接池不够用的时候,最大会创建50个connection)
maxActive=50
#最小空闲连接 (当连接池空闲没有被使用的时候, 就会减少到5个)
minIdle=5
#超时等待连接 (当连接数量超过最大连接数的时候,会等待5秒,5秒后就会抛出异常)
maxWait=5000
3、修改JDBCUtils工具类

使用druid给连接池对象赋值

6-17行的代码是修改的内容(对比之前的JDBCUtil是)

public class JDBCDriudUtils {
    //定义数据库连接池对象
    private static DataSource dataSource;

    static {
        try {
            //读取配置文件中的数据源信息
            Properties properties = new Properties();
            //通过类加载器架加载classpath下的配置文件
            InputStream in = JDBCDriudUtils.class.getClassLoader().getResourceAsStream("druid.properties");
            properties.load(in);
            dataSource = DruidDataSourceFactory.createDataSource(properties);

        } catch (Exception e) {

        }
    }

    //获取连接池对象
    public static DataSource getDataSource() {
        return dataSource;
    }

    static ThreadLocal<Connection> tl = new ThreadLocal<>();
    //修改Connection的注册,保证同一线程下,不同事务拿到的T是同一个
    //获取connection连接的方法
    public static Connection getConnection() {
        try {
            Connection connection = tl.get();
            if(connection == null) {
                connection = dataSource.getConnection();
            }
            return connection;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }




    //关闭释放资源
    public static void closeAll(Connection connection, Statement statement, ResultSet resultSet) {
        try {
            if(resultSet != null) resultSet.close();
            if(statement != null) statement.close();
            if(connection != null) {
                connection.close(); //此时归还connection到连接池,但是ThreadLocal中还有保存这个connection,但是关闭了,下次不能用
                tl.remove();//移除关闭的connection
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }


    /**
     * 事务的封装
     */
    //开启事务
    public static void start() {
        Connection connection  = getConnection();
        try {
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //提交事务
    public static void commit() {
        Connection connection = getConnection();
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            closeAll(connection, null, null);
        }
    }

    //回滚事务
    public static void rollback() {
        Connection connection = getConnection();
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            closeAll(connection, null, null);
        }
    }

}

20220-03-0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值